ejv 2.1.1 → 2.1.3

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.
Files changed (45) hide show
  1. package/.mocharc.json +8 -8
  2. package/CHANGELOG.md +134 -135
  3. package/LICENSE +21 -21
  4. package/README-KR.md +604 -597
  5. package/README.md +608 -603
  6. package/build/cjs/constants.js +1 -0
  7. package/build/cjs/constants.js.map +1 -1
  8. package/build/cjs/ejv.js +3 -0
  9. package/build/cjs/ejv.js.map +1 -1
  10. package/build/cjs/package.json +1 -1
  11. package/build/cjs/tester.js +11 -1
  12. package/build/cjs/tester.js.map +1 -1
  13. package/build/constants.d.ts +1 -0
  14. package/build/esm/constants.js +1 -0
  15. package/build/esm/constants.js.map +1 -1
  16. package/build/esm/ejv.js +4 -1
  17. package/build/esm/ejv.js.map +1 -1
  18. package/build/esm/package.json +1 -1
  19. package/build/esm/tester.js +9 -0
  20. package/build/esm/tester.js.map +1 -1
  21. package/build/tester.d.ts +1 -0
  22. package/eslint.config.mjs +66 -59
  23. package/package.json +56 -54
  24. package/scripts/add-js-extensions.ts +59 -59
  25. package/spec/ArrayScheme.ts +1021 -1021
  26. package/spec/CommonScheme.ts +251 -251
  27. package/spec/DateScheme.ts +472 -472
  28. package/spec/NumberScheme.ts +1160 -1160
  29. package/spec/ObjectScheme.ts +499 -499
  30. package/spec/RegExpScheme.ts +112 -112
  31. package/spec/StringScheme.ts +1407 -1336
  32. package/spec/common-test-util.ts +63 -63
  33. package/spec/ejv.spec.ts +235 -235
  34. package/spec/testers.spec.ts +833 -833
  35. package/src/constants.ts +164 -162
  36. package/src/ejv.ts +1751 -1746
  37. package/src/index.ts +14 -14
  38. package/src/interfaces.ts +144 -144
  39. package/src/tester.ts +323 -312
  40. package/src/util.ts +124 -124
  41. package/tsconfig.cjs.json +8 -8
  42. package/tsconfig.esm.json +7 -7
  43. package/tsconfig.json +19 -19
  44. package/tsconfig.scripts.json +14 -14
  45. package/tsconfig.types.json +9 -9
package/src/tester.ts CHANGED
@@ -1,312 +1,323 @@
1
- import { DATA_TYPE } from './constants';
2
- import { AnyObject } from './interfaces';
3
-
4
- export const typeTester = (value: unknown, type: DATA_TYPE): boolean => {
5
- let valid: boolean;
6
-
7
- switch (type) {
8
- case DATA_TYPE.BOOLEAN:
9
- valid = booleanTester(value);
10
- break;
11
-
12
- case DATA_TYPE.NUMBER:
13
- valid = numberTester(value);
14
- break;
15
-
16
- case DATA_TYPE.STRING:
17
- valid = stringTester(value);
18
- break;
19
-
20
- case DATA_TYPE.OBJECT:
21
- valid = objectTester(value);
22
- break;
23
-
24
- case DATA_TYPE.DATE:
25
- valid = dateTester(value);
26
- break;
27
-
28
- case DATA_TYPE.REGEXP:
29
- valid = regExpTester(value);
30
- break;
31
-
32
- case DATA_TYPE.ARRAY:
33
- valid = arrayTester(value);
34
- break;
35
- }
36
-
37
- return valid;
38
- };
39
-
40
- export const definedTester = (value: unknown): value is boolean => {
41
- return value !== undefined;
42
- };
43
-
44
- export const enumTester = <T> (value: T, arr: T[]): boolean => {
45
- return arr.includes(value);
46
- };
47
-
48
- export const notEnumTester = <T> (value: T, arr: T[]): boolean => {
49
- return !enumTester(value, arr);
50
- };
51
-
52
- export const lengthTester = (value: string | unknown[], length: number): boolean => {
53
- return value.length === length;
54
- };
55
-
56
- export const minLengthTester = (value: string | unknown[], minLength: number): boolean => {
57
- return value.length >= minLength;
58
- };
59
-
60
- export const maxLengthTester = (value: string | unknown[], maxLength: number): boolean => {
61
- return value.length <= maxLength;
62
- };
63
-
64
- export const booleanTester = (value: unknown): value is boolean => {
65
- return typeof value === 'boolean';
66
- };
67
-
68
- export const numberTester = (value: unknown): value is number => {
69
- return typeof value === 'number' && !isNaN(value);
70
- };
71
-
72
- export const integerTester = (value: number): boolean => {
73
- return +value.toFixed(0) === value;
74
- };
75
-
76
- export const indexTester = (value: number): value is number => {
77
- return integerTester(value) && value >= 0;
78
- };
79
-
80
- export const minNumberTester = (value: number, min: number): boolean => {
81
- return value >= min;
82
- };
83
-
84
- export const exclusiveMinNumberTester = (value: number, min: number): boolean => {
85
- return value > min;
86
- };
87
-
88
- export const maxNumberTester = (value: number, max: number): boolean => {
89
- return value <= max;
90
- };
91
-
92
- export const exclusiveMaxNumberTester = (value: number, max: number): boolean => {
93
- return value < max;
94
- };
95
-
96
- export const stringTester = (value: unknown): value is string => {
97
- return typeof value === 'string';
98
- };
99
-
100
- export const stringRegExpTester = (value: string, regExp: string | RegExp): boolean => {
101
- let valid = false;
102
-
103
- let _regExp: RegExp | undefined = undefined;
104
-
105
- if (regExpTester(regExp)) {
106
- _regExp = regExp as RegExp;
107
- }
108
- else if (stringTester(regExp)) {
109
- _regExp = new RegExp(regExp);
110
- }
111
-
112
- if (!!_regExp && regExpTester(_regExp)) {
113
- valid = _regExp.test(value);
114
- }
115
-
116
- return valid;
117
- };
118
-
119
- // RFC 5322, 3.4.1. spec
120
- export const emailTester = (value: string): boolean => {
121
- let valid = false;
122
-
123
- if (stringTester(value) && stringRegExpTester(value, /^.+@.+$/)) {
124
- const valueAsString: string = value as string;
125
-
126
- const atIndex: number = valueAsString.lastIndexOf('@');
127
- const localPart: string = valueAsString.substr(0, atIndex);
128
- const domain: string = valueAsString.substr(atIndex + 1);
129
-
130
- // regular expression sources
131
- // const aTextRegExpStr : string = '[-a-zA-Z0-9!#$%&\\\'*+/=?^_`{|}~]+';
132
-
133
- const dotAtomRegExp = /^(\.?[-a-zA-Z0-9!#$%&'*+/=?^_`{|}~]+)*$/;
134
- const quotedStringRegExp = /^"[\u0020-\u005b\u005d-\u007e\\]*"$/; // include space (\u005b)
135
- const domainLiteralRegExp = /^\[[\u0020-\u005a\u005c-\u007e\\]*]$/;
136
-
137
- const validLocalPart: boolean = localPart.length <= 64
138
- && (
139
- dotAtomRegExp.test(localPart)
140
- || quotedStringRegExp.test(localPart)
141
- );
142
-
143
- const validDomain: boolean = !domain.startsWith('.') && !domain.endsWith('.')
144
- && (
145
- dotAtomRegExp.test(domain)
146
- || domainLiteralRegExp.test(domain)
147
- );
148
-
149
- valid = validLocalPart && validDomain;
150
- }
151
-
152
- return valid;
153
- };
154
-
155
- // RFC 3339 (https://www.ietf.org/rfc/rfc3339.txt) : YYYY-MM-DDThh:mm:ss[.SSSZ]
156
- const rfc3339Tester = (value: string): boolean => {
157
- return /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([0-1][0-9]|2[0-3])(:([0-5][0-9])){2}(\.\d+)?(Z|[-+]\d{2}:\d{2})?$/.test(value);
158
- };
159
-
160
- const iso8601DateTester = (value: string): boolean => {
161
- const years = '(\\d{4})';
162
- const months = '(0[1-9]|1[0-2])';
163
- const dates = '(0[1-9]|[1-2][0-9]|3[0-1])';
164
- const dateOfYear = '(00[1-9]|0[1-9][0-9]|[1-2]\\d{2}|3[0-5]\\d|36[0-6])'; // 366 for leap year
165
- const weeks = '(W(0[1-9]|[2-4][0-9]|5[0-3]))';
166
- const days = '[1-7]';
167
-
168
- return [
169
- new RegExp(`^[-+]?${ years }$`), // years : YYYY, +YYYY, -YYYY
170
- new RegExp(`^${ years }-${ months }(-${ dates })?$`), // calendar dates : YYYY-MM-DD, YYYY-MM
171
- new RegExp(`^${ years }${ months }${ dates }$`), // calendar dates : YYYYMMDD
172
- new RegExp(`^--${ months }-?${ dates }$`), // calendar dates : --MM-DD, --MMDD
173
- new RegExp(`^${ years }-${ weeks }(-${ days })?$`), // week dates : YYYY-Www, YYYY-Www-D
174
- new RegExp(`^${ years }${ weeks }(${ days })?$`), // week dates : YYYYWww, YYYYWwwD
175
- new RegExp(`^${ years }-?${ dateOfYear }$`) // ordinal dates : YYYY-DDD, YYYYDDD
176
- ].some((regExp: RegExp): boolean => {
177
- return regExp.test(value);
178
- });
179
- };
180
-
181
- const iso8601TimeTester = (value: string): boolean => {
182
- const hours = '([0-1]\\d|2[0-3])';
183
- const minutes = '([0-5]\\d)';
184
- const seconds = '([0-5]\\d|60)'; // 60 for leap second
185
- const ms = '(\\.[0-9]+)';
186
-
187
- return [
188
- new RegExp(`^(${ hours }|24)$`), // hh
189
- new RegExp(`^((${ hours }:${ minutes })|24:00)$`), // hh:mm
190
- new RegExp(`^((${ hours }:${ minutes }:${ seconds })|24:00:00)$`), // hh:mm:ss
191
- new RegExp(`^((${ hours }:${ minutes }:${ seconds }${ ms })|24:00:00.0+)$`), // hh:mm:ss
192
-
193
- new RegExp(`^(${ hours }${ minutes }|2400)$`), // hhmm
194
- new RegExp(`^(${ hours }${ minutes }${ seconds }|240000)$`), // hhmmss
195
- new RegExp(`^(${ hours }${ minutes }${ seconds }${ ms }|240000.0+)$`) // hhmmss.sss
196
- ]
197
- .some((regExp: RegExp): boolean => {
198
- return regExp.test(value);
199
- });
200
- };
201
-
202
- const iso8601DateTimeTester = (value: string): boolean => {
203
- let valid = false;
204
-
205
- if (/.+T.+/.test(value) // should have 1 'T'
206
- && /(Z|[-+]\d{2}:?\d{2})$/.test(value) // should end with 'Z' or timezone
207
- ) {
208
- const dateAndTime: string[] = value.split('T');
209
- const date: string = dateAndTime[0];
210
- let time: string = dateAndTime[1];
211
-
212
- if (time.endsWith('Z')) {
213
- time = time.replace('Z', '');
214
- }
215
- else {
216
- const timezoneStartIndex: number = time.includes('+')
217
- ? time.indexOf('+')
218
- : time.indexOf('-');
219
-
220
- time = time.slice(0, timezoneStartIndex);
221
- }
222
-
223
- valid = iso8601DateTester(date) && iso8601TimeTester(time);
224
- }
225
-
226
- return valid;
227
- };
228
-
229
- export const dateFormatTester = (value: string): boolean => {
230
- return iso8601DateTester(value);
231
- };
232
-
233
- export const timeFormatTester = (value: string): boolean => {
234
- return iso8601TimeTester(value);
235
- };
236
-
237
- export const dateTimeFormatTester = (value: string): boolean => {
238
- return rfc3339Tester(value) || iso8601DateTimeTester(value);
239
- };
240
-
241
- // // with port
242
- // export const urlTester = (value : any) : boolean => {
243
- // return stringTester(value)
244
- // && /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(value);
245
- // };
246
- //
247
- // // TODO: with port
248
- // export const ipv4Tester = (value : any) : boolean => {
249
- // return stringTester(value)
250
- // && /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(value);
251
- // };
252
- //
253
- // export const ipv6Tester = (value : any) : boolean => {
254
- // return stringTester(value)
255
- // && /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$;/.test(value);
256
- // };
257
- //
258
- // export const ipTester = (value : any) : boolean => {
259
- // return ipv4Tester(value) || ipv6Tester(value);
260
- // };
261
-
262
- export const objectTester = (value: unknown): boolean => {
263
- return typeof value === 'object';
264
- };
265
-
266
- export const hasPropertyTester = (value: AnyObject): boolean => {
267
- return Object.keys(value).length > 0;
268
- };
269
-
270
- export const dateTester = (value: unknown): value is Date => {
271
- return objectTester(value)
272
- && value !== null
273
- && typeof value === 'object'
274
- && value instanceof Date
275
- && !isNaN(value.getFullYear());
276
- };
277
-
278
- export const minDateTester = (value: Date, min: Date): boolean => {
279
- return +value >= +min;
280
- };
281
-
282
- export const exclusiveMinDateTester = (value: Date, min: Date): boolean => {
283
- return +value > +min;
284
- };
285
-
286
- export const maxDateTester = (value: Date, max: Date): boolean => {
287
- return +value <= +max;
288
- };
289
-
290
- export const exclusiveMaxDateTester = (value: Date, max: Date): boolean => {
291
- return +value < +max;
292
- };
293
-
294
- export const arrayTester = (value: unknown): value is unknown[] => {
295
- return Array.isArray(value);
296
- };
297
-
298
- export const arrayTypeOfTester = (array: unknown[], type: DATA_TYPE): boolean => {
299
- return array.every((item: unknown): boolean => {
300
- return typeTester(item, type);
301
- });
302
- };
303
-
304
- export const uniqueItemsTester = (array: unknown[]): boolean => {
305
- return array.every((item: unknown): boolean => {
306
- return array.filter((target: unknown): boolean => target === item).length === 1;
307
- });
308
- };
309
-
310
- export const regExpTester = (value: unknown): value is RegExp => {
311
- return value instanceof RegExp;
312
- };
1
+ import { DATA_TYPE } from './constants';
2
+ import { AnyObject } from './interfaces';
3
+
4
+ export const typeTester = (value: unknown, type: DATA_TYPE): boolean => {
5
+ let valid: boolean;
6
+
7
+ switch (type) {
8
+ case DATA_TYPE.BOOLEAN:
9
+ valid = booleanTester(value);
10
+ break;
11
+
12
+ case DATA_TYPE.NUMBER:
13
+ valid = numberTester(value);
14
+ break;
15
+
16
+ case DATA_TYPE.STRING:
17
+ valid = stringTester(value);
18
+ break;
19
+
20
+ case DATA_TYPE.OBJECT:
21
+ valid = objectTester(value);
22
+ break;
23
+
24
+ case DATA_TYPE.DATE:
25
+ valid = dateTester(value);
26
+ break;
27
+
28
+ case DATA_TYPE.REGEXP:
29
+ valid = regExpTester(value);
30
+ break;
31
+
32
+ case DATA_TYPE.ARRAY:
33
+ valid = arrayTester(value);
34
+ break;
35
+ }
36
+
37
+ return valid;
38
+ };
39
+
40
+ export const definedTester = (value: unknown): value is boolean => {
41
+ return value !== undefined;
42
+ };
43
+
44
+ export const enumTester = <T> (value: T, arr: T[]): boolean => {
45
+ return arr.includes(value);
46
+ };
47
+
48
+ export const notEnumTester = <T> (value: T, arr: T[]): boolean => {
49
+ return !enumTester(value, arr);
50
+ };
51
+
52
+ export const lengthTester = (value: string | unknown[], length: number): boolean => {
53
+ return value.length === length;
54
+ };
55
+
56
+ export const minLengthTester = (value: string | unknown[], minLength: number): boolean => {
57
+ return value.length >= minLength;
58
+ };
59
+
60
+ export const maxLengthTester = (value: string | unknown[], maxLength: number): boolean => {
61
+ return value.length <= maxLength;
62
+ };
63
+
64
+ export const booleanTester = (value: unknown): value is boolean => {
65
+ return typeof value === 'boolean';
66
+ };
67
+
68
+ export const numberTester = (value: unknown): value is number => {
69
+ return typeof value === 'number' && !isNaN(value);
70
+ };
71
+
72
+ export const integerTester = (value: number): boolean => {
73
+ return +value.toFixed(0) === value;
74
+ };
75
+
76
+ export const indexTester = (value: number): value is number => {
77
+ return integerTester(value) && value >= 0;
78
+ };
79
+
80
+ export const minNumberTester = (value: number, min: number): boolean => {
81
+ return value >= min;
82
+ };
83
+
84
+ export const exclusiveMinNumberTester = (value: number, min: number): boolean => {
85
+ return value > min;
86
+ };
87
+
88
+ export const maxNumberTester = (value: number, max: number): boolean => {
89
+ return value <= max;
90
+ };
91
+
92
+ export const exclusiveMaxNumberTester = (value: number, max: number): boolean => {
93
+ return value < max;
94
+ };
95
+
96
+ export const stringTester = (value: unknown): value is string => {
97
+ return typeof value === 'string';
98
+ };
99
+
100
+ export const stringRegExpTester = (value: string, regExp: string | RegExp): boolean => {
101
+ let valid = false;
102
+
103
+ let _regExp: RegExp | undefined = undefined;
104
+
105
+ if (regExpTester(regExp)) {
106
+ _regExp = regExp as RegExp;
107
+ }
108
+ else if (stringTester(regExp)) {
109
+ _regExp = new RegExp(regExp);
110
+ }
111
+
112
+ if (!!_regExp && regExpTester(_regExp)) {
113
+ valid = _regExp.test(value);
114
+ }
115
+
116
+ return valid;
117
+ };
118
+
119
+ // RFC 5322, 3.4.1. spec
120
+ export const emailTester = (value: string): boolean => {
121
+ let valid = false;
122
+
123
+ if (stringTester(value) && stringRegExpTester(value, /^.+@.+$/)) {
124
+ const valueAsString: string = value as string;
125
+
126
+ const atIndex: number = valueAsString.lastIndexOf('@');
127
+ const localPart: string = valueAsString.substr(0, atIndex);
128
+ const domain: string = valueAsString.substr(atIndex + 1);
129
+
130
+ // regular expression sources
131
+ // const aTextRegExpStr : string = '[-a-zA-Z0-9!#$%&\\\'*+/=?^_`{|}~]+';
132
+
133
+ const dotAtomRegExp = /^(\.?[-a-zA-Z0-9!#$%&'*+/=?^_`{|}~]+)*$/;
134
+ const quotedStringRegExp = /^"[\u0020-\u005b\u005d-\u007e\\]*"$/; // include space (\u005b)
135
+ const domainLiteralRegExp = /^\[[\u0020-\u005a\u005c-\u007e\\]*]$/;
136
+
137
+ const validLocalPart: boolean = localPart.length <= 64
138
+ && (
139
+ dotAtomRegExp.test(localPart)
140
+ || quotedStringRegExp.test(localPart)
141
+ );
142
+
143
+ const validDomain: boolean = !domain.startsWith('.') && !domain.endsWith('.')
144
+ && (
145
+ dotAtomRegExp.test(domain)
146
+ || domainLiteralRegExp.test(domain)
147
+ );
148
+
149
+ valid = validLocalPart && validDomain;
150
+ }
151
+
152
+ return valid;
153
+ };
154
+
155
+ export const jsonStrTester = (value: string): boolean => {
156
+ try {
157
+ JSON.parse(value);
158
+
159
+ return true;
160
+ }
161
+ catch (e: unknown) { // eslint-disable-line @typescript-eslint/no-unused-vars
162
+ return false;
163
+ }
164
+ };
165
+
166
+ // RFC 3339 (https://www.ietf.org/rfc/rfc3339.txt) : YYYY-MM-DDThh:mm:ss[.SSSZ]
167
+ const rfc3339Tester = (value: string): boolean => {
168
+ return /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([0-1][0-9]|2[0-3])(:([0-5][0-9])){2}(\.\d+)?(Z|[-+]\d{2}:\d{2})?$/.test(value);
169
+ };
170
+
171
+ const iso8601DateTester = (value: string): boolean => {
172
+ const years = '(\\d{4})';
173
+ const months = '(0[1-9]|1[0-2])';
174
+ const dates = '(0[1-9]|[1-2][0-9]|3[0-1])';
175
+ const dateOfYear = '(00[1-9]|0[1-9][0-9]|[1-2]\\d{2}|3[0-5]\\d|36[0-6])'; // 366 for leap year
176
+ const weeks = '(W(0[1-9]|[2-4][0-9]|5[0-3]))';
177
+ const days = '[1-7]';
178
+
179
+ return [
180
+ new RegExp(`^[-+]?${ years }$`), // years : YYYY, +YYYY, -YYYY
181
+ new RegExp(`^${ years }-${ months }(-${ dates })?$`), // calendar dates : YYYY-MM-DD, YYYY-MM
182
+ new RegExp(`^${ years }${ months }${ dates }$`), // calendar dates : YYYYMMDD
183
+ new RegExp(`^--${ months }-?${ dates }$`), // calendar dates : --MM-DD, --MMDD
184
+ new RegExp(`^${ years }-${ weeks }(-${ days })?$`), // week dates : YYYY-Www, YYYY-Www-D
185
+ new RegExp(`^${ years }${ weeks }(${ days })?$`), // week dates : YYYYWww, YYYYWwwD
186
+ new RegExp(`^${ years }-?${ dateOfYear }$`) // ordinal dates : YYYY-DDD, YYYYDDD
187
+ ].some((regExp: RegExp): boolean => {
188
+ return regExp.test(value);
189
+ });
190
+ };
191
+
192
+ const iso8601TimeTester = (value: string): boolean => {
193
+ const hours = '([0-1]\\d|2[0-3])';
194
+ const minutes = '([0-5]\\d)';
195
+ const seconds = '([0-5]\\d|60)'; // 60 for leap second
196
+ const ms = '(\\.[0-9]+)';
197
+
198
+ return [
199
+ new RegExp(`^(${ hours }|24)$`), // hh
200
+ new RegExp(`^((${ hours }:${ minutes })|24:00)$`), // hh:mm
201
+ new RegExp(`^((${ hours }:${ minutes }:${ seconds })|24:00:00)$`), // hh:mm:ss
202
+ new RegExp(`^((${ hours }:${ minutes }:${ seconds }${ ms })|24:00:00.0+)$`), // hh:mm:ss
203
+
204
+ new RegExp(`^(${ hours }${ minutes }|2400)$`), // hhmm
205
+ new RegExp(`^(${ hours }${ minutes }${ seconds }|240000)$`), // hhmmss
206
+ new RegExp(`^(${ hours }${ minutes }${ seconds }${ ms }|240000.0+)$`) // hhmmss.sss
207
+ ]
208
+ .some((regExp: RegExp): boolean => {
209
+ return regExp.test(value);
210
+ });
211
+ };
212
+
213
+ const iso8601DateTimeTester = (value: string): boolean => {
214
+ let valid = false;
215
+
216
+ if (/.+T.+/.test(value) // should have 1 'T'
217
+ && /(Z|[-+]\d{2}:?\d{2})$/.test(value) // should end with 'Z' or timezone
218
+ ) {
219
+ const dateAndTime: string[] = value.split('T');
220
+ const date: string = dateAndTime[0];
221
+ let time: string = dateAndTime[1];
222
+
223
+ if (time.endsWith('Z')) {
224
+ time = time.replace('Z', '');
225
+ }
226
+ else {
227
+ const timezoneStartIndex: number = time.includes('+')
228
+ ? time.indexOf('+')
229
+ : time.indexOf('-');
230
+
231
+ time = time.slice(0, timezoneStartIndex);
232
+ }
233
+
234
+ valid = iso8601DateTester(date) && iso8601TimeTester(time);
235
+ }
236
+
237
+ return valid;
238
+ };
239
+
240
+ export const dateFormatTester = (value: string): boolean => {
241
+ return iso8601DateTester(value);
242
+ };
243
+
244
+ export const timeFormatTester = (value: string): boolean => {
245
+ return iso8601TimeTester(value);
246
+ };
247
+
248
+ export const dateTimeFormatTester = (value: string): boolean => {
249
+ return rfc3339Tester(value) || iso8601DateTimeTester(value);
250
+ };
251
+
252
+ // // with port
253
+ // export const urlTester = (value : any) : boolean => {
254
+ // return stringTester(value)
255
+ // && /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(value);
256
+ // };
257
+ //
258
+ // // TODO: with port
259
+ // export const ipv4Tester = (value : any) : boolean => {
260
+ // return stringTester(value)
261
+ // && /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(value);
262
+ // };
263
+ //
264
+ // export const ipv6Tester = (value : any) : boolean => {
265
+ // return stringTester(value)
266
+ // && /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$;/.test(value);
267
+ // };
268
+ //
269
+ // export const ipTester = (value : any) : boolean => {
270
+ // return ipv4Tester(value) || ipv6Tester(value);
271
+ // };
272
+
273
+ export const objectTester = (value: unknown): boolean => {
274
+ return typeof value === 'object';
275
+ };
276
+
277
+ export const hasPropertyTester = (value: AnyObject): boolean => {
278
+ return Object.keys(value).length > 0;
279
+ };
280
+
281
+ export const dateTester = (value: unknown): value is Date => {
282
+ return objectTester(value)
283
+ && value !== null
284
+ && typeof value === 'object'
285
+ && value instanceof Date
286
+ && !isNaN(value.getFullYear());
287
+ };
288
+
289
+ export const minDateTester = (value: Date, min: Date): boolean => {
290
+ return +value >= +min;
291
+ };
292
+
293
+ export const exclusiveMinDateTester = (value: Date, min: Date): boolean => {
294
+ return +value > +min;
295
+ };
296
+
297
+ export const maxDateTester = (value: Date, max: Date): boolean => {
298
+ return +value <= +max;
299
+ };
300
+
301
+ export const exclusiveMaxDateTester = (value: Date, max: Date): boolean => {
302
+ return +value < +max;
303
+ };
304
+
305
+ export const arrayTester = (value: unknown): value is unknown[] => {
306
+ return Array.isArray(value);
307
+ };
308
+
309
+ export const arrayTypeOfTester = (array: unknown[], type: DATA_TYPE): boolean => {
310
+ return array.every((item: unknown): boolean => {
311
+ return typeTester(item, type);
312
+ });
313
+ };
314
+
315
+ export const uniqueItemsTester = (array: unknown[]): boolean => {
316
+ return array.every((item: unknown): boolean => {
317
+ return array.filter((target: unknown): boolean => target === item).length === 1;
318
+ });
319
+ };
320
+
321
+ export const regExpTester = (value: unknown): value is RegExp => {
322
+ return value instanceof RegExp;
323
+ };