@orchestr-sh/orchestr 1.9.15 → 1.10.0

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 (111) hide show
  1. package/dist/Database/Contracts/QueryBuilderInterface.d.ts +11 -1
  2. package/dist/Database/Contracts/QueryBuilderInterface.d.ts.map +1 -1
  3. package/dist/Database/Query/Builder.d.ts +16 -1
  4. package/dist/Database/Query/Builder.d.ts.map +1 -1
  5. package/dist/Database/Query/Builder.js +82 -1
  6. package/dist/Database/Query/Builder.js.map +1 -1
  7. package/dist/Foundation/Http/Rule.d.ts +102 -0
  8. package/dist/Foundation/Http/Rule.d.ts.map +1 -0
  9. package/dist/Foundation/Http/Rule.js +277 -0
  10. package/dist/Foundation/Http/Rule.js.map +1 -0
  11. package/dist/Foundation/Http/Rules/AnyOfRule.d.ts +8 -0
  12. package/dist/Foundation/Http/Rules/AnyOfRule.d.ts.map +1 -0
  13. package/dist/Foundation/Http/Rules/AnyOfRule.js +28 -0
  14. package/dist/Foundation/Http/Rules/AnyOfRule.js.map +1 -0
  15. package/dist/Foundation/Http/Rules/ArrayRule.d.ts +5 -0
  16. package/dist/Foundation/Http/Rules/ArrayRule.d.ts.map +1 -0
  17. package/dist/Foundation/Http/Rules/ArrayRule.js +11 -0
  18. package/dist/Foundation/Http/Rules/ArrayRule.js.map +1 -0
  19. package/dist/Foundation/Http/Rules/Base.d.ts +13 -0
  20. package/dist/Foundation/Http/Rules/Base.d.ts.map +1 -0
  21. package/dist/Foundation/Http/Rules/Base.js +23 -0
  22. package/dist/Foundation/Http/Rules/Base.js.map +1 -0
  23. package/dist/Foundation/Http/Rules/CanRule.d.ts +5 -0
  24. package/dist/Foundation/Http/Rules/CanRule.d.ts.map +1 -0
  25. package/dist/Foundation/Http/Rules/CanRule.js +11 -0
  26. package/dist/Foundation/Http/Rules/CanRule.js.map +1 -0
  27. package/dist/Foundation/Http/Rules/ContainsRule.d.ts +8 -0
  28. package/dist/Foundation/Http/Rules/ContainsRule.d.ts.map +1 -0
  29. package/dist/Foundation/Http/Rules/ContainsRule.js +21 -0
  30. package/dist/Foundation/Http/Rules/ContainsRule.js.map +1 -0
  31. package/dist/Foundation/Http/Rules/DatabaseRule.d.ts +5 -0
  32. package/dist/Foundation/Http/Rules/DatabaseRule.d.ts.map +1 -0
  33. package/dist/Foundation/Http/Rules/DatabaseRule.js +11 -0
  34. package/dist/Foundation/Http/Rules/DatabaseRule.js.map +1 -0
  35. package/dist/Foundation/Http/Rules/DateRule.d.ts +5 -0
  36. package/dist/Foundation/Http/Rules/DateRule.d.ts.map +1 -0
  37. package/dist/Foundation/Http/Rules/DateRule.js +11 -0
  38. package/dist/Foundation/Http/Rules/DateRule.js.map +1 -0
  39. package/dist/Foundation/Http/Rules/DimensionsRule.d.ts +17 -0
  40. package/dist/Foundation/Http/Rules/DimensionsRule.d.ts.map +1 -0
  41. package/dist/Foundation/Http/Rules/DimensionsRule.js +38 -0
  42. package/dist/Foundation/Http/Rules/DimensionsRule.js.map +1 -0
  43. package/dist/Foundation/Http/Rules/DoesntContainRule.d.ts +8 -0
  44. package/dist/Foundation/Http/Rules/DoesntContainRule.d.ts.map +1 -0
  45. package/dist/Foundation/Http/Rules/DoesntContainRule.js +21 -0
  46. package/dist/Foundation/Http/Rules/DoesntContainRule.js.map +1 -0
  47. package/dist/Foundation/Http/Rules/EmailRule.d.ts +5 -0
  48. package/dist/Foundation/Http/Rules/EmailRule.d.ts.map +1 -0
  49. package/dist/Foundation/Http/Rules/EmailRule.js +11 -0
  50. package/dist/Foundation/Http/Rules/EmailRule.js.map +1 -0
  51. package/dist/Foundation/Http/Rules/EnumRule.d.ts +8 -0
  52. package/dist/Foundation/Http/Rules/EnumRule.d.ts.map +1 -0
  53. package/dist/Foundation/Http/Rules/EnumRule.js +21 -0
  54. package/dist/Foundation/Http/Rules/EnumRule.js.map +1 -0
  55. package/dist/Foundation/Http/Rules/ExcludeIfRule.d.ts +5 -0
  56. package/dist/Foundation/Http/Rules/ExcludeIfRule.d.ts.map +1 -0
  57. package/dist/Foundation/Http/Rules/ExcludeIfRule.js +11 -0
  58. package/dist/Foundation/Http/Rules/ExcludeIfRule.js.map +1 -0
  59. package/dist/Foundation/Http/Rules/ExistsRule.d.ts +5 -0
  60. package/dist/Foundation/Http/Rules/ExistsRule.d.ts.map +1 -0
  61. package/dist/Foundation/Http/Rules/ExistsRule.js +11 -0
  62. package/dist/Foundation/Http/Rules/ExistsRule.js.map +1 -0
  63. package/dist/Foundation/Http/Rules/FileRule.d.ts +7 -0
  64. package/dist/Foundation/Http/Rules/FileRule.d.ts.map +1 -0
  65. package/dist/Foundation/Http/Rules/FileRule.js +23 -0
  66. package/dist/Foundation/Http/Rules/FileRule.js.map +1 -0
  67. package/dist/Foundation/Http/Rules/ImageFileRule.d.ts +7 -0
  68. package/dist/Foundation/Http/Rules/ImageFileRule.d.ts.map +1 -0
  69. package/dist/Foundation/Http/Rules/ImageFileRule.js +22 -0
  70. package/dist/Foundation/Http/Rules/ImageFileRule.js.map +1 -0
  71. package/dist/Foundation/Http/Rules/InRule.d.ts +5 -0
  72. package/dist/Foundation/Http/Rules/InRule.d.ts.map +1 -0
  73. package/dist/Foundation/Http/Rules/InRule.js +11 -0
  74. package/dist/Foundation/Http/Rules/InRule.js.map +1 -0
  75. package/dist/Foundation/Http/Rules/NotInRule.d.ts +5 -0
  76. package/dist/Foundation/Http/Rules/NotInRule.d.ts.map +1 -0
  77. package/dist/Foundation/Http/Rules/NotInRule.js +11 -0
  78. package/dist/Foundation/Http/Rules/NotInRule.js.map +1 -0
  79. package/dist/Foundation/Http/Rules/NumericRule.d.ts +5 -0
  80. package/dist/Foundation/Http/Rules/NumericRule.d.ts.map +1 -0
  81. package/dist/Foundation/Http/Rules/NumericRule.js +11 -0
  82. package/dist/Foundation/Http/Rules/NumericRule.js.map +1 -0
  83. package/dist/Foundation/Http/Rules/PasswordRule.d.ts +5 -0
  84. package/dist/Foundation/Http/Rules/PasswordRule.d.ts.map +1 -0
  85. package/dist/Foundation/Http/Rules/PasswordRule.js +11 -0
  86. package/dist/Foundation/Http/Rules/PasswordRule.js.map +1 -0
  87. package/dist/Foundation/Http/Rules/ProhibitedIfRule.d.ts +5 -0
  88. package/dist/Foundation/Http/Rules/ProhibitedIfRule.d.ts.map +1 -0
  89. package/dist/Foundation/Http/Rules/ProhibitedIfRule.js +11 -0
  90. package/dist/Foundation/Http/Rules/ProhibitedIfRule.js.map +1 -0
  91. package/dist/Foundation/Http/Rules/RequiredIfRule.d.ts +5 -0
  92. package/dist/Foundation/Http/Rules/RequiredIfRule.d.ts.map +1 -0
  93. package/dist/Foundation/Http/Rules/RequiredIfRule.js +11 -0
  94. package/dist/Foundation/Http/Rules/RequiredIfRule.js.map +1 -0
  95. package/dist/Foundation/Http/Rules/UniqueRule.d.ts +5 -0
  96. package/dist/Foundation/Http/Rules/UniqueRule.d.ts.map +1 -0
  97. package/dist/Foundation/Http/Rules/UniqueRule.js +11 -0
  98. package/dist/Foundation/Http/Rules/UniqueRule.js.map +1 -0
  99. package/dist/Foundation/Http/Rules/index.d.ts +22 -0
  100. package/dist/Foundation/Http/Rules/index.d.ts.map +1 -0
  101. package/dist/Foundation/Http/Rules/index.js +38 -0
  102. package/dist/Foundation/Http/Rules/index.js.map +1 -0
  103. package/dist/Foundation/Http/Validator.d.ts +1 -1
  104. package/dist/Foundation/Http/Validator.d.ts.map +1 -1
  105. package/dist/Foundation/Http/Validator.js +838 -9
  106. package/dist/Foundation/Http/Validator.js.map +1 -1
  107. package/dist/index.d.ts +1 -0
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +6 -4
  110. package/dist/index.js.map +1 -1
  111. package/package.json +1 -1
@@ -30,16 +30,174 @@ class Validator {
30
30
  this.validatedFields = {};
31
31
  for (const [field, rule] of Object.entries(this.rules)) {
32
32
  const rules = this.parseRules(rule);
33
+ const items = typeof rule === 'string'
34
+ ? rule.split('|').map((r) => r.trim())
35
+ : Array.isArray(rule)
36
+ ? rule
37
+ : typeof rule === 'object'
38
+ ? [rule]
39
+ : [];
33
40
  const value = this.getFieldValue(field);
34
- for (const ruleName of rules) {
35
- const [ruleKey, ...params] = ruleName.split(':');
36
- const result = await this.validateRule(field, value, ruleKey, params);
37
- if (!result.passes) {
38
- this.addError(field, result.message);
41
+ const hasSometimes = rules.includes('sometimes');
42
+ const hasNullable = rules.includes('nullable');
43
+ let excluded = false;
44
+ if (hasSometimes && value === undefined) {
45
+ continue;
46
+ }
47
+ if (hasNullable && (value === undefined || value === null)) {
48
+ if (rules.includes('required')) {
49
+ const result = await this.validateRule(field, value, 'required', []);
50
+ if (!result.passes) {
51
+ this.addError(field, result.message);
52
+ }
53
+ else {
54
+ this.validatedFields[field] = value;
55
+ }
56
+ }
57
+ else {
58
+ this.validatedFields[field] = value;
59
+ }
60
+ continue;
61
+ }
62
+ for (const item of items) {
63
+ if (excluded) {
64
+ break;
65
+ }
66
+ if (typeof item === 'string') {
67
+ const [ruleKey, ...params] = item.split(':');
68
+ if (ruleKey === 'exclude') {
69
+ excluded = true;
70
+ break;
71
+ }
72
+ if (ruleKey === 'exclude_if') {
73
+ const [otherField, expected] = params;
74
+ const ov = this.getFieldValue(otherField);
75
+ if (String(ov) === expected) {
76
+ excluded = true;
77
+ break;
78
+ }
79
+ }
80
+ if (ruleKey === 'exclude_unless') {
81
+ const [otherField, expected] = params;
82
+ const ov = this.getFieldValue(otherField);
83
+ if (String(ov) !== expected) {
84
+ excluded = true;
85
+ break;
86
+ }
87
+ }
88
+ if (ruleKey === 'exclude_with') {
89
+ const fields = params
90
+ .join(':')
91
+ .split(',')
92
+ .map((p) => p.trim())
93
+ .filter((p) => p.length > 0);
94
+ if (fields.some((f) => this.getFieldValue(f) !== undefined)) {
95
+ excluded = true;
96
+ break;
97
+ }
98
+ }
99
+ if (ruleKey === 'exclude_without') {
100
+ const fields = params
101
+ .join(':')
102
+ .split(',')
103
+ .map((p) => p.trim())
104
+ .filter((p) => p.length > 0);
105
+ if (fields.some((f) => this.getFieldValue(f) === undefined)) {
106
+ excluded = true;
107
+ break;
108
+ }
109
+ }
110
+ const result = await this.validateRule(field, value, ruleKey, params);
111
+ if (!result.passes) {
112
+ this.addError(field, result.message);
113
+ }
114
+ continue;
115
+ }
116
+ if (typeof item === 'object' &&
117
+ item &&
118
+ item.invokable === true &&
119
+ typeof item.passes === 'function') {
120
+ const attribute = this.getAttribute(field);
121
+ const ctx = {
122
+ field,
123
+ attribute,
124
+ other: (name) => this.getFieldValue(name),
125
+ helpers: {
126
+ isPresent: (v) => v !== undefined,
127
+ isEmpty: (v) => v === undefined || v === null || v === '',
128
+ toNumber: (v) => (typeof v === 'number' ? v : parseFloat(String(v))),
129
+ toString: (v) => (typeof v === 'string' ? v : String(v)),
130
+ isDate: (v) => {
131
+ const d = new Date(v);
132
+ return !isNaN(d.getTime());
133
+ },
134
+ },
135
+ };
136
+ const passes = await item.passes(attribute, value, ctx);
137
+ if (!passes) {
138
+ const custom = typeof item.messageFor === 'function'
139
+ ? item.messageFor(attribute, value, ctx)
140
+ : typeof item.message === 'function'
141
+ ? item.message(attribute, value, ctx)
142
+ : item.message;
143
+ const msg = custom || this.getMessage(field, item.rule || 'rule', `The ${attribute} is invalid.`);
144
+ this.addError(field, msg);
145
+ }
146
+ continue;
147
+ }
148
+ if (typeof item === 'object' && item && 'rule' in item) {
149
+ const [ruleKey, ...params] = String(item.rule).split(':');
150
+ if (ruleKey === 'exclude') {
151
+ excluded = true;
152
+ break;
153
+ }
154
+ if (ruleKey === 'exclude_if') {
155
+ const [otherField, expected] = params;
156
+ const ov = this.getFieldValue(otherField);
157
+ if (String(ov) === expected) {
158
+ excluded = true;
159
+ break;
160
+ }
161
+ }
162
+ if (ruleKey === 'exclude_unless') {
163
+ const [otherField, expected] = params;
164
+ const ov = this.getFieldValue(otherField);
165
+ if (String(ov) !== expected) {
166
+ excluded = true;
167
+ break;
168
+ }
169
+ }
170
+ if (ruleKey === 'exclude_with') {
171
+ const fields = params
172
+ .join(':')
173
+ .split(',')
174
+ .map((p) => p.trim())
175
+ .filter((p) => p.length > 0);
176
+ if (fields.some((f) => this.getFieldValue(f) !== undefined)) {
177
+ excluded = true;
178
+ break;
179
+ }
180
+ }
181
+ if (ruleKey === 'exclude_without') {
182
+ const fields = params
183
+ .join(':')
184
+ .split(',')
185
+ .map((p) => p.trim())
186
+ .filter((p) => p.length > 0);
187
+ if (fields.some((f) => this.getFieldValue(f) === undefined)) {
188
+ excluded = true;
189
+ break;
190
+ }
191
+ }
192
+ const result = await this.validateRule(field, value, ruleKey, params);
193
+ if (!result.passes) {
194
+ this.addError(field, result.message);
195
+ }
196
+ continue;
39
197
  }
40
198
  }
41
199
  // If no errors for this field, add to validated data
42
- if (!this.errorMessages[field]) {
200
+ if (!excluded && !this.errorMessages[field]) {
43
201
  this.validatedFields[field] = value;
44
202
  }
45
203
  }
@@ -53,7 +211,17 @@ class Validator {
53
211
  return rule.split('|').map((r) => r.trim());
54
212
  }
55
213
  if (Array.isArray(rule)) {
56
- return rule;
214
+ return rule
215
+ .map((r) => {
216
+ if (typeof r === 'string') {
217
+ return r.trim();
218
+ }
219
+ if (typeof r === 'object' && 'rule' in r) {
220
+ return r.rule;
221
+ }
222
+ return '';
223
+ })
224
+ .filter((r) => r.length > 0);
57
225
  }
58
226
  if (typeof rule === 'object' && 'rule' in rule) {
59
227
  return [rule.rule];
@@ -81,7 +249,43 @@ class Validator {
81
249
  */
82
250
  async validateRule(field, value, rule, params) {
83
251
  const attribute = this.getAttribute(field);
252
+ const listParams = params.length
253
+ ? params
254
+ .join(':')
255
+ .split(/[,:]/)
256
+ .map((p) => p.trim())
257
+ .filter((p) => p.length > 0)
258
+ : [];
259
+ const other = (name) => this.getFieldValue(name);
260
+ const isPresent = (v) => v !== undefined;
261
+ const isEmpty = (v) => v === undefined || v === null || v === '';
262
+ const toNumber = (v) => (typeof v === 'number' ? v : parseFloat(String(v)));
263
+ const toString = (v) => (typeof v === 'string' ? v : String(v));
264
+ const isDate = (v) => {
265
+ const d = new Date(v);
266
+ return !isNaN(d.getTime());
267
+ };
84
268
  switch (rule) {
269
+ case 'bail':
270
+ return { passes: true, message: '' };
271
+ case 'exclude':
272
+ return { passes: true, message: '' };
273
+ case 'exclude_if': {
274
+ const [otherField, expected] = params;
275
+ const ov = other(otherField);
276
+ if (toString(ov) === expected) {
277
+ return { passes: true, message: '' };
278
+ }
279
+ return { passes: true, message: '' };
280
+ }
281
+ case 'exclude_unless': {
282
+ const [otherField, expected] = params;
283
+ const ov = other(otherField);
284
+ if (toString(ov) !== expected) {
285
+ return { passes: true, message: '' };
286
+ }
287
+ return { passes: true, message: '' };
288
+ }
85
289
  case 'required':
86
290
  if (value === undefined || value === null || value === '') {
87
291
  return {
@@ -90,6 +294,148 @@ class Validator {
90
294
  };
91
295
  }
92
296
  return { passes: true, message: '' };
297
+ case 'required_if': {
298
+ const [otherField, expected] = params;
299
+ const ov = other(otherField);
300
+ if (toString(ov) === expected && isEmpty(value)) {
301
+ return {
302
+ passes: false,
303
+ message: this.getMessage(field, rule, `The ${attribute} field is required.`),
304
+ };
305
+ }
306
+ return { passes: true, message: '' };
307
+ }
308
+ case 'required_unless': {
309
+ const [otherField, expected] = params;
310
+ const ov = other(otherField);
311
+ if (toString(ov) !== expected && isEmpty(value)) {
312
+ return {
313
+ passes: false,
314
+ message: this.getMessage(field, rule, `The ${attribute} field is required.`),
315
+ };
316
+ }
317
+ return { passes: true, message: '' };
318
+ }
319
+ case 'required_with': {
320
+ const others = listParams;
321
+ if (others.some((o) => isPresent(other(o))) && isEmpty(value)) {
322
+ return {
323
+ passes: false,
324
+ message: this.getMessage(field, rule, `The ${attribute} field is required.`),
325
+ };
326
+ }
327
+ return { passes: true, message: '' };
328
+ }
329
+ case 'required_with_all': {
330
+ const others = listParams;
331
+ if (others.every((o) => isPresent(other(o))) && isEmpty(value)) {
332
+ return {
333
+ passes: false,
334
+ message: this.getMessage(field, rule, `The ${attribute} field is required.`),
335
+ };
336
+ }
337
+ return { passes: true, message: '' };
338
+ }
339
+ case 'required_without': {
340
+ const others = listParams;
341
+ if (others.some((o) => !isPresent(other(o))) && isEmpty(value)) {
342
+ return {
343
+ passes: false,
344
+ message: this.getMessage(field, rule, `The ${attribute} field is required.`),
345
+ };
346
+ }
347
+ return { passes: true, message: '' };
348
+ }
349
+ case 'required_without_all': {
350
+ const others = listParams;
351
+ if (others.every((o) => !isPresent(other(o))) && isEmpty(value)) {
352
+ return {
353
+ passes: false,
354
+ message: this.getMessage(field, rule, `The ${attribute} field is required.`),
355
+ };
356
+ }
357
+ return { passes: true, message: '' };
358
+ }
359
+ case 'present':
360
+ if (!isPresent(value)) {
361
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} field must be present.`) };
362
+ }
363
+ return { passes: true, message: '' };
364
+ case 'present_if': {
365
+ const [otherField, expected] = params;
366
+ const ov = other(otherField);
367
+ if (toString(ov) === expected && !isPresent(value)) {
368
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} field must be present.`) };
369
+ }
370
+ return { passes: true, message: '' };
371
+ }
372
+ case 'present_unless': {
373
+ const [otherField, expected] = params;
374
+ const ov = other(otherField);
375
+ if (toString(ov) !== expected && !isPresent(value)) {
376
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} field must be present.`) };
377
+ }
378
+ return { passes: true, message: '' };
379
+ }
380
+ case 'present_with': {
381
+ const fields = listParams;
382
+ if (fields.some((f) => isPresent(other(f))) && !isPresent(value)) {
383
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} field must be present.`) };
384
+ }
385
+ return { passes: true, message: '' };
386
+ }
387
+ case 'present_with_all': {
388
+ const fields = listParams;
389
+ if (fields.every((f) => isPresent(other(f))) && !isPresent(value)) {
390
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} field must be present.`) };
391
+ }
392
+ return { passes: true, message: '' };
393
+ }
394
+ case 'filled':
395
+ if (isPresent(value) && isEmpty(value)) {
396
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must have a value.`) };
397
+ }
398
+ return { passes: true, message: '' };
399
+ case 'accepted': {
400
+ const acceptedValues = ['yes', 'on', '1', 1, true, 'true'];
401
+ if (!acceptedValues.includes(value)) {
402
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be accepted.`) };
403
+ }
404
+ return { passes: true, message: '' };
405
+ }
406
+ case 'accepted_if': {
407
+ const [otherField, expected] = params;
408
+ const ov = other(otherField);
409
+ if (toString(ov) === expected) {
410
+ const acceptedValues = ['yes', 'on', '1', 1, true, 'true'];
411
+ if (!acceptedValues.includes(value)) {
412
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be accepted.`) };
413
+ }
414
+ }
415
+ return { passes: true, message: '' };
416
+ }
417
+ case 'declined': {
418
+ const declinedValues = ['no', 'off', '0', 0, false, 'false'];
419
+ if (!declinedValues.includes(value)) {
420
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be declined.`) };
421
+ }
422
+ return { passes: true, message: '' };
423
+ }
424
+ case 'declined_if': {
425
+ const [otherField, expected] = params;
426
+ const ov = other(otherField);
427
+ if (toString(ov) === expected) {
428
+ const declinedValues = ['no', 'off', '0', 0, false, 'false'];
429
+ if (!declinedValues.includes(value)) {
430
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be declined.`) };
431
+ }
432
+ }
433
+ return { passes: true, message: '' };
434
+ }
435
+ case 'nullable':
436
+ return { passes: true, message: '' };
437
+ case 'sometimes':
438
+ return { passes: true, message: '' };
93
439
  case 'email':
94
440
  if (value && !this.isValidEmail(value)) {
95
441
  return {
@@ -179,8 +525,24 @@ class Validator {
179
525
  }
180
526
  return { passes: true, message: '' };
181
527
  }
528
+ case 'size': {
529
+ const sizeValue = params[0];
530
+ if (typeof value === 'string' && value.length !== parseInt(sizeValue)) {
531
+ return {
532
+ passes: false,
533
+ message: this.getMessage(field, rule, `The ${attribute} must be ${sizeValue} characters.`),
534
+ };
535
+ }
536
+ if (typeof value === 'number' && value !== parseFloat(sizeValue)) {
537
+ return {
538
+ passes: false,
539
+ message: this.getMessage(field, rule, `The ${attribute} must be ${sizeValue}.`),
540
+ };
541
+ }
542
+ return { passes: true, message: '' };
543
+ }
182
544
  case 'in':
183
- if (value && !params.includes(String(value))) {
545
+ if (value && !listParams.includes(String(value))) {
184
546
  return {
185
547
  passes: false,
186
548
  message: this.getMessage(field, rule, `The selected ${attribute} is invalid.`),
@@ -188,7 +550,7 @@ class Validator {
188
550
  }
189
551
  return { passes: true, message: '' };
190
552
  case 'not_in':
191
- if (value && params.includes(String(value))) {
553
+ if (value && listParams.includes(String(value))) {
192
554
  return {
193
555
  passes: false,
194
556
  message: this.getMessage(field, rule, `The selected ${attribute} is invalid.`),
@@ -203,6 +565,20 @@ class Validator {
203
565
  };
204
566
  }
205
567
  return { passes: true, message: '' };
568
+ case 'required_array_keys': {
569
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
570
+ const keys = listParams;
571
+ const hasAll = keys.every((k) => Object.prototype.hasOwnProperty.call(value, k));
572
+ if (!hasAll) {
573
+ return {
574
+ passes: false,
575
+ message: this.getMessage(field, rule, `The ${attribute} must contain keys: ${keys.join(', ')}.`),
576
+ };
577
+ }
578
+ return { passes: true, message: '' };
579
+ }
580
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be an array.`) };
581
+ }
206
582
  case 'confirmed': {
207
583
  const confirmationField = `${field}_confirmation`;
208
584
  const confirmationValue = this.data[confirmationField];
@@ -256,6 +632,459 @@ class Validator {
256
632
  }
257
633
  return { passes: true, message: '' };
258
634
  }
635
+ case 'not_regex': {
636
+ const pattern = new RegExp(params.join(':'));
637
+ if (value && pattern.test(value)) {
638
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} format is invalid.`) };
639
+ }
640
+ return { passes: true, message: '' };
641
+ }
642
+ case 'json':
643
+ if (value) {
644
+ try {
645
+ JSON.parse(typeof value === 'string' ? value : String(value));
646
+ }
647
+ catch {
648
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be a valid JSON.`) };
649
+ }
650
+ }
651
+ return { passes: true, message: '' };
652
+ case 'ascii':
653
+ if (value && /[^\x00-\x7F]/.test(toString(value))) {
654
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be ASCII characters.`) };
655
+ }
656
+ return { passes: true, message: '' };
657
+ case 'decimal': {
658
+ const places = params[0] ? parseInt(params[0]) : undefined;
659
+ if (value !== undefined) {
660
+ const s = toString(value);
661
+ const m = s.match(/^[-+]?\d+(\.(\d+))?$/);
662
+ if (!m) {
663
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be a decimal.`) };
664
+ }
665
+ if (places !== undefined) {
666
+ const dp = m[2]?.length || 0;
667
+ if (dp !== places) {
668
+ return {
669
+ passes: false,
670
+ message: this.getMessage(field, rule, `The ${attribute} must have ${places} decimal places.`),
671
+ };
672
+ }
673
+ }
674
+ }
675
+ return { passes: true, message: '' };
676
+ }
677
+ case 'starts_with':
678
+ if (value) {
679
+ const s = toString(value);
680
+ if (!listParams.some((p) => s.startsWith(p))) {
681
+ return {
682
+ passes: false,
683
+ message: this.getMessage(field, rule, `The ${attribute} must start with one of: ${listParams.join(', ')}.`),
684
+ };
685
+ }
686
+ }
687
+ return { passes: true, message: '' };
688
+ case 'ends_with':
689
+ if (value) {
690
+ const s = toString(value);
691
+ if (!listParams.some((p) => s.endsWith(p))) {
692
+ return {
693
+ passes: false,
694
+ message: this.getMessage(field, rule, `The ${attribute} must end with one of: ${listParams.join(', ')}.`),
695
+ };
696
+ }
697
+ }
698
+ return { passes: true, message: '' };
699
+ case 'doesnt_start_with':
700
+ if (value) {
701
+ const s = toString(value);
702
+ if (listParams.some((p) => s.startsWith(p))) {
703
+ return {
704
+ passes: false,
705
+ message: this.getMessage(field, rule, `The ${attribute} must not start with one of: ${listParams.join(', ')}.`),
706
+ };
707
+ }
708
+ }
709
+ return { passes: true, message: '' };
710
+ case 'doesnt_end_with':
711
+ if (value) {
712
+ const s = toString(value);
713
+ if (listParams.some((p) => s.endsWith(p))) {
714
+ return {
715
+ passes: false,
716
+ message: this.getMessage(field, rule, `The ${attribute} must not end with one of: ${listParams.join(', ')}.`),
717
+ };
718
+ }
719
+ }
720
+ return { passes: true, message: '' };
721
+ case 'distinct':
722
+ if (Array.isArray(value)) {
723
+ const set = new Set(value.map((v) => JSON.stringify(v)));
724
+ if (set.size !== value.length) {
725
+ return {
726
+ passes: false,
727
+ message: this.getMessage(field, rule, `The ${attribute} must not have duplicates.`),
728
+ };
729
+ }
730
+ }
731
+ return { passes: true, message: '' };
732
+ case 'same': {
733
+ const [otherField] = params;
734
+ if (toString(value) !== toString(other(otherField))) {
735
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must match ${otherField}.`) };
736
+ }
737
+ return { passes: true, message: '' };
738
+ }
739
+ case 'different': {
740
+ const [otherField] = params;
741
+ if (toString(value) === toString(other(otherField))) {
742
+ return {
743
+ passes: false,
744
+ message: this.getMessage(field, rule, `The ${attribute} must be different from ${otherField}.`),
745
+ };
746
+ }
747
+ return { passes: true, message: '' };
748
+ }
749
+ case 'gt': {
750
+ const [otherFieldOrValue] = params;
751
+ const cmp = isNaN(Number(otherFieldOrValue)) ? other(otherFieldOrValue) : parseFloat(otherFieldOrValue);
752
+ if (!(toNumber(value) > toNumber(cmp))) {
753
+ return {
754
+ passes: false,
755
+ message: this.getMessage(field, rule, `The ${attribute} must be greater than ${otherFieldOrValue}.`),
756
+ };
757
+ }
758
+ return { passes: true, message: '' };
759
+ }
760
+ case 'gte': {
761
+ const [otherFieldOrValue] = params;
762
+ const cmp = isNaN(Number(otherFieldOrValue)) ? other(otherFieldOrValue) : parseFloat(otherFieldOrValue);
763
+ if (!(toNumber(value) >= toNumber(cmp))) {
764
+ return {
765
+ passes: false,
766
+ message: this.getMessage(field, rule, `The ${attribute} must be greater than or equal to ${otherFieldOrValue}.`),
767
+ };
768
+ }
769
+ return { passes: true, message: '' };
770
+ }
771
+ case 'lt': {
772
+ const [otherFieldOrValue] = params;
773
+ const cmp = isNaN(Number(otherFieldOrValue)) ? other(otherFieldOrValue) : parseFloat(otherFieldOrValue);
774
+ if (!(toNumber(value) < toNumber(cmp))) {
775
+ return {
776
+ passes: false,
777
+ message: this.getMessage(field, rule, `The ${attribute} must be less than ${otherFieldOrValue}.`),
778
+ };
779
+ }
780
+ return { passes: true, message: '' };
781
+ }
782
+ case 'lte': {
783
+ const [otherFieldOrValue] = params;
784
+ const cmp = isNaN(Number(otherFieldOrValue)) ? other(otherFieldOrValue) : parseFloat(otherFieldOrValue);
785
+ if (!(toNumber(value) <= toNumber(cmp))) {
786
+ return {
787
+ passes: false,
788
+ message: this.getMessage(field, rule, `The ${attribute} must be less than or equal to ${otherFieldOrValue}.`),
789
+ };
790
+ }
791
+ return { passes: true, message: '' };
792
+ }
793
+ case 'uuid':
794
+ if (value &&
795
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{4}-[0-9a-f]{12}$/i.test(toString(value))) {
796
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be a valid UUID.`) };
797
+ }
798
+ return { passes: true, message: '' };
799
+ case 'timezone':
800
+ if (value) {
801
+ const v = toString(value);
802
+ const zones = Intl.supportedValuesOf ? Intl.supportedValuesOf('timeZone') : [];
803
+ if (!zones.includes(v)) {
804
+ return {
805
+ passes: false,
806
+ message: this.getMessage(field, rule, `The ${attribute} must be a valid timezone.`),
807
+ };
808
+ }
809
+ }
810
+ return { passes: true, message: '' };
811
+ case 'date':
812
+ if (value && !isDate(value)) {
813
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} is not a valid date.`) };
814
+ }
815
+ return { passes: true, message: '' };
816
+ case 'date_equals':
817
+ if (value) {
818
+ const d1 = new Date(value).getTime();
819
+ const d2 = new Date(params[0]).getTime();
820
+ if (isNaN(d1) || isNaN(d2) || d1 !== d2) {
821
+ return {
822
+ passes: false,
823
+ message: this.getMessage(field, rule, `The ${attribute} must equal ${params[0]}.`),
824
+ };
825
+ }
826
+ }
827
+ return { passes: true, message: '' };
828
+ case 'after': {
829
+ const cmp = params[0];
830
+ const v = new Date(value).getTime();
831
+ const ov = other(cmp);
832
+ const c = isDate(ov) ? new Date(ov).getTime() : new Date(cmp).getTime();
833
+ if (isNaN(v) || isNaN(c) || v <= c) {
834
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be after ${cmp}.`) };
835
+ }
836
+ return { passes: true, message: '' };
837
+ }
838
+ case 'after_or_equal': {
839
+ const cmp = params[0];
840
+ const v = new Date(value).getTime();
841
+ const ov = other(cmp);
842
+ const c = isDate(ov) ? new Date(ov).getTime() : new Date(cmp).getTime();
843
+ if (isNaN(v) || isNaN(c) || v < c) {
844
+ return {
845
+ passes: false,
846
+ message: this.getMessage(field, rule, `The ${attribute} must be after or equal to ${cmp}.`),
847
+ };
848
+ }
849
+ return { passes: true, message: '' };
850
+ }
851
+ case 'before': {
852
+ const cmp = params[0];
853
+ const v = new Date(value).getTime();
854
+ const ov = other(cmp);
855
+ const c = isDate(ov) ? new Date(ov).getTime() : new Date(cmp).getTime();
856
+ if (isNaN(v) || isNaN(c) || v >= c) {
857
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be before ${cmp}.`) };
858
+ }
859
+ return { passes: true, message: '' };
860
+ }
861
+ case 'before_or_equal': {
862
+ const cmp = params[0];
863
+ const v = new Date(value).getTime();
864
+ const ov = other(cmp);
865
+ const c = isDate(ov) ? new Date(ov).getTime() : new Date(cmp).getTime();
866
+ if (isNaN(v) || isNaN(c) || v > c) {
867
+ return {
868
+ passes: false,
869
+ message: this.getMessage(field, rule, `The ${attribute} must be before or equal to ${cmp}.`),
870
+ };
871
+ }
872
+ return { passes: true, message: '' };
873
+ }
874
+ case 'digits': {
875
+ const len = parseInt(params[0]);
876
+ if (!/^\d+$/.test(String(value)) || String(value).length !== len) {
877
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be ${len} digits.`) };
878
+ }
879
+ return { passes: true, message: '' };
880
+ }
881
+ case 'digits_between': {
882
+ const minD = parseInt(params[0]);
883
+ const maxD = parseInt(params[1]);
884
+ const s = String(value);
885
+ if (!/^\d+$/.test(s) || s.length < minD || s.length > maxD) {
886
+ return {
887
+ passes: false,
888
+ message: this.getMessage(field, rule, `The ${attribute} must be between ${minD} and ${maxD} digits.`),
889
+ };
890
+ }
891
+ return { passes: true, message: '' };
892
+ }
893
+ case 'min_digits': {
894
+ const minD = parseInt(params[0]);
895
+ const s = String(value);
896
+ if (!/^\d+$/.test(s) || s.length < minD) {
897
+ return {
898
+ passes: false,
899
+ message: this.getMessage(field, rule, `The ${attribute} must be at least ${minD} digits.`),
900
+ };
901
+ }
902
+ return { passes: true, message: '' };
903
+ }
904
+ case 'max_digits': {
905
+ const maxD = parseInt(params[0]);
906
+ const s = String(value);
907
+ if (!/^\d+$/.test(s) || s.length > maxD) {
908
+ return {
909
+ passes: false,
910
+ message: this.getMessage(field, rule, `The ${attribute} must be at most ${maxD} digits.`),
911
+ };
912
+ }
913
+ return { passes: true, message: '' };
914
+ }
915
+ case 'multiple_of': {
916
+ const base = parseFloat(params[0]);
917
+ if (Number.isFinite(base) && toNumber(value) % base !== 0) {
918
+ return {
919
+ passes: false,
920
+ message: this.getMessage(field, rule, `The ${attribute} must be a multiple of ${base}.`),
921
+ };
922
+ }
923
+ return { passes: true, message: '' };
924
+ }
925
+ case 'ip':
926
+ if (value && !/^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/.test(toString(value))) {
927
+ return {
928
+ passes: false,
929
+ message: this.getMessage(field, rule, `The ${attribute} must be a valid IP address.`),
930
+ };
931
+ }
932
+ return { passes: true, message: '' };
933
+ case 'ipv4':
934
+ if (value && !/^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/.test(toString(value))) {
935
+ return {
936
+ passes: false,
937
+ message: this.getMessage(field, rule, `The ${attribute} must be a valid IPv4 address.`),
938
+ };
939
+ }
940
+ return { passes: true, message: '' };
941
+ case 'ipv6':
942
+ if (value && !/^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}$/i.test(toString(value))) {
943
+ return {
944
+ passes: false,
945
+ message: this.getMessage(field, rule, `The ${attribute} must be a valid IPv6 address.`),
946
+ };
947
+ }
948
+ return { passes: true, message: '' };
949
+ case 'mac_address':
950
+ if (value && !/^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/.test(toString(value))) {
951
+ return {
952
+ passes: false,
953
+ message: this.getMessage(field, rule, `The ${attribute} must be a valid MAC address.`),
954
+ };
955
+ }
956
+ return { passes: true, message: '' };
957
+ case 'lowercase':
958
+ if (value && toString(value) !== toString(value).toLowerCase()) {
959
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be lowercase.`) };
960
+ }
961
+ return { passes: true, message: '' };
962
+ case 'uppercase':
963
+ if (value && toString(value) !== toString(value).toUpperCase()) {
964
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be uppercase.`) };
965
+ }
966
+ return { passes: true, message: '' };
967
+ case 'in_array': {
968
+ const [otherField] = params;
969
+ const arr = other(otherField);
970
+ if (!Array.isArray(arr) || !arr.map((v) => String(v)).includes(String(value))) {
971
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be in ${otherField}.`) };
972
+ }
973
+ return { passes: true, message: '' };
974
+ }
975
+ case 'prohibited':
976
+ if (isPresent(value)) {
977
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} field is prohibited.`) };
978
+ }
979
+ return { passes: true, message: '' };
980
+ case 'prohibited_if': {
981
+ const [otherField, expected] = params;
982
+ const ov = other(otherField);
983
+ if (toString(ov) === expected && isPresent(value)) {
984
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} field is prohibited.`) };
985
+ }
986
+ return { passes: true, message: '' };
987
+ }
988
+ case 'prohibited_unless': {
989
+ const [otherField, expected] = params;
990
+ const ov = other(otherField);
991
+ if (toString(ov) !== expected && isPresent(value)) {
992
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} field is prohibited.`) };
993
+ }
994
+ return { passes: true, message: '' };
995
+ }
996
+ case 'prohibits': {
997
+ const others = listParams;
998
+ if (isPresent(value) && others.some((o) => isPresent(other(o)))) {
999
+ return {
1000
+ passes: false,
1001
+ message: this.getMessage(field, rule, `The ${attribute} prohibits ${others.join(', ')} from being present.`),
1002
+ };
1003
+ }
1004
+ return { passes: true, message: '' };
1005
+ }
1006
+ case 'file':
1007
+ if (value && typeof value !== 'object') {
1008
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be a file.`) };
1009
+ }
1010
+ return { passes: true, message: '' };
1011
+ case 'image':
1012
+ if (value && typeof value === 'object') {
1013
+ const type = (value.mimetype || value.type || '').toString();
1014
+ if (!type.startsWith('image/')) {
1015
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be an image.`) };
1016
+ }
1017
+ }
1018
+ return { passes: true, message: '' };
1019
+ case 'mimetypes': {
1020
+ const allowed = listParams;
1021
+ if (value && typeof value === 'object') {
1022
+ const type = (value.mimetype || value.type || '').toString();
1023
+ if (!allowed.includes(type)) {
1024
+ return {
1025
+ passes: false,
1026
+ message: this.getMessage(field, rule, `The ${attribute} has an invalid mimetype.`),
1027
+ };
1028
+ }
1029
+ }
1030
+ return { passes: true, message: '' };
1031
+ }
1032
+ case 'mimes': {
1033
+ const allowed = listParams;
1034
+ if (value && typeof value === 'object') {
1035
+ const type = (value.mimetype || value.type || '').toString();
1036
+ const ext = (value.extension || '').toString();
1037
+ if (!(allowed.includes(ext) || allowed.some((m) => type.endsWith(`/${m}`)))) {
1038
+ return {
1039
+ passes: false,
1040
+ message: this.getMessage(field, rule, `The ${attribute} has an invalid file type.`),
1041
+ };
1042
+ }
1043
+ }
1044
+ return { passes: true, message: '' };
1045
+ }
1046
+ case 'active_url':
1047
+ if (value && !this.isValidUrl(value)) {
1048
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} is not a valid URL.`) };
1049
+ }
1050
+ return { passes: true, message: '' };
1051
+ case 'missing':
1052
+ if (isPresent(value) && !isEmpty(value)) {
1053
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be missing.`) };
1054
+ }
1055
+ return { passes: true, message: '' };
1056
+ case 'missing_if': {
1057
+ const [otherField, expected] = params;
1058
+ const ov = other(otherField);
1059
+ if (toString(ov) === expected && isPresent(value) && !isEmpty(value)) {
1060
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be missing.`) };
1061
+ }
1062
+ return { passes: true, message: '' };
1063
+ }
1064
+ case 'missing_unless': {
1065
+ const [otherField, expected] = params;
1066
+ const ov = other(otherField);
1067
+ if (toString(ov) !== expected && isPresent(value) && !isEmpty(value)) {
1068
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be missing.`) };
1069
+ }
1070
+ return { passes: true, message: '' };
1071
+ }
1072
+ case 'missing_with': {
1073
+ const fields = listParams;
1074
+ if (fields.some((f) => isPresent(other(f))) && isPresent(value) && !isEmpty(value)) {
1075
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be missing.`) };
1076
+ }
1077
+ return { passes: true, message: '' };
1078
+ }
1079
+ case 'missing_with_all': {
1080
+ const fields = listParams;
1081
+ if (fields.every((f) => isPresent(other(f))) && isPresent(value) && !isEmpty(value)) {
1082
+ return { passes: false, message: this.getMessage(field, rule, `The ${attribute} must be missing.`) };
1083
+ }
1084
+ return { passes: true, message: '' };
1085
+ }
1086
+ case 'current_password':
1087
+ return { passes: true, message: '' };
259
1088
  default:
260
1089
  console.warn(`Unknown validation rule: ${rule}`);
261
1090
  return { passes: true, message: '' };