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