@fogpipe/forma-core 0.10.3 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-RG7JYFQ6.js → chunk-7ANLNW3X.js} +39 -13
- package/dist/{chunk-RG7JYFQ6.js.map → chunk-7ANLNW3X.js.map} +1 -1
- package/dist/engine/index.cjs +38 -12
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.js +1 -1
- package/dist/engine/validate.d.ts.map +1 -1
- package/dist/index.cjs +38 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/__tests__/validate.test.ts +345 -0
- package/src/engine/validate.ts +54 -15
|
@@ -275,4 +275,349 @@ describe("validate", () => {
|
|
|
275
275
|
expect(result.errors[0].message).toBe("Name must be at least 2 characters");
|
|
276
276
|
});
|
|
277
277
|
});
|
|
278
|
+
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// Array validation
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
describe("array validation", () => {
|
|
284
|
+
describe("minItems/maxItems from schema", () => {
|
|
285
|
+
it("should validate minItems from schema when fieldDef does not specify it", () => {
|
|
286
|
+
const spec: Forma = {
|
|
287
|
+
version: "1.0",
|
|
288
|
+
meta: { id: "test", title: "Test" },
|
|
289
|
+
schema: {
|
|
290
|
+
type: "object",
|
|
291
|
+
properties: {
|
|
292
|
+
items: {
|
|
293
|
+
type: "array",
|
|
294
|
+
items: { type: "string" },
|
|
295
|
+
minItems: 2,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
fields: {
|
|
300
|
+
items: { label: "Items", type: "array" },
|
|
301
|
+
},
|
|
302
|
+
fieldOrder: ["items"],
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// Valid - has minimum 2 items
|
|
306
|
+
expect(validate({ items: ["a", "b"] }, spec).valid).toBe(true);
|
|
307
|
+
expect(validate({ items: ["a", "b", "c"] }, spec).valid).toBe(true);
|
|
308
|
+
|
|
309
|
+
// Invalid - less than minItems
|
|
310
|
+
const result = validate({ items: ["a"] }, spec);
|
|
311
|
+
expect(result.valid).toBe(false);
|
|
312
|
+
expect(result.errors[0].field).toBe("items");
|
|
313
|
+
expect(result.errors[0].message).toBe("Items must have at least 2 items");
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("should validate maxItems from schema when fieldDef does not specify it", () => {
|
|
317
|
+
const spec: Forma = {
|
|
318
|
+
version: "1.0",
|
|
319
|
+
meta: { id: "test", title: "Test" },
|
|
320
|
+
schema: {
|
|
321
|
+
type: "object",
|
|
322
|
+
properties: {
|
|
323
|
+
tags: {
|
|
324
|
+
type: "array",
|
|
325
|
+
items: { type: "string" },
|
|
326
|
+
maxItems: 3,
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
fields: {
|
|
331
|
+
tags: { label: "Tags", type: "array" },
|
|
332
|
+
},
|
|
333
|
+
fieldOrder: ["tags"],
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Valid - has maximum 3 items
|
|
337
|
+
expect(validate({ tags: ["a", "b", "c"] }, spec).valid).toBe(true);
|
|
338
|
+
expect(validate({ tags: ["a"] }, spec).valid).toBe(true);
|
|
339
|
+
|
|
340
|
+
// Invalid - more than maxItems
|
|
341
|
+
const result = validate({ tags: ["a", "b", "c", "d"] }, spec);
|
|
342
|
+
expect(result.valid).toBe(false);
|
|
343
|
+
expect(result.errors[0].field).toBe("tags");
|
|
344
|
+
expect(result.errors[0].message).toBe("Tags must have no more than 3 items");
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("should allow fieldDef.minItems to override schema.minItems", () => {
|
|
348
|
+
const spec: Forma = {
|
|
349
|
+
version: "1.0",
|
|
350
|
+
meta: { id: "test", title: "Test" },
|
|
351
|
+
schema: {
|
|
352
|
+
type: "object",
|
|
353
|
+
properties: {
|
|
354
|
+
items: {
|
|
355
|
+
type: "array",
|
|
356
|
+
items: { type: "string" },
|
|
357
|
+
minItems: 5, // schema says 5
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
fields: {
|
|
362
|
+
items: {
|
|
363
|
+
label: "Items",
|
|
364
|
+
type: "array",
|
|
365
|
+
minItems: 2, // fieldDef overrides to 2
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
fieldOrder: ["items"],
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// fieldDef.minItems (2) should take precedence over schema.minItems (5)
|
|
372
|
+
expect(validate({ items: ["a", "b"] }, spec).valid).toBe(true);
|
|
373
|
+
expect(validate({ items: ["a"] }, spec).valid).toBe(false);
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
describe("array item validation from schema", () => {
|
|
378
|
+
it("should validate array item type constraints from schema", () => {
|
|
379
|
+
const spec: Forma = {
|
|
380
|
+
version: "1.0",
|
|
381
|
+
meta: { id: "test", title: "Test" },
|
|
382
|
+
schema: {
|
|
383
|
+
type: "object",
|
|
384
|
+
properties: {
|
|
385
|
+
scores: {
|
|
386
|
+
type: "array",
|
|
387
|
+
items: {
|
|
388
|
+
type: "object",
|
|
389
|
+
properties: {
|
|
390
|
+
name: { type: "string" },
|
|
391
|
+
score: { type: "number", minimum: 0, maximum: 100 },
|
|
392
|
+
},
|
|
393
|
+
required: ["name", "score"],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
fields: {
|
|
399
|
+
scores: { label: "Scores", type: "array" },
|
|
400
|
+
},
|
|
401
|
+
fieldOrder: ["scores"],
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// Valid data
|
|
405
|
+
const validResult = validate(
|
|
406
|
+
{
|
|
407
|
+
scores: [
|
|
408
|
+
{ name: "Alice", score: 95 },
|
|
409
|
+
{ name: "Bob", score: 87 },
|
|
410
|
+
],
|
|
411
|
+
},
|
|
412
|
+
spec
|
|
413
|
+
);
|
|
414
|
+
expect(validResult.valid).toBe(true);
|
|
415
|
+
|
|
416
|
+
// Invalid - score above maximum
|
|
417
|
+
const maxResult = validate(
|
|
418
|
+
{
|
|
419
|
+
scores: [
|
|
420
|
+
{ name: "Alice", score: 105 },
|
|
421
|
+
{ name: "Bob", score: 87 },
|
|
422
|
+
],
|
|
423
|
+
},
|
|
424
|
+
spec
|
|
425
|
+
);
|
|
426
|
+
expect(maxResult.valid).toBe(false);
|
|
427
|
+
expect(maxResult.errors[0].field).toBe("scores[0].score");
|
|
428
|
+
expect(maxResult.errors[0].message).toContain("no more than 100");
|
|
429
|
+
|
|
430
|
+
// Invalid - score below minimum
|
|
431
|
+
const minResult = validate(
|
|
432
|
+
{
|
|
433
|
+
scores: [
|
|
434
|
+
{ name: "Alice", score: -5 },
|
|
435
|
+
{ name: "Bob", score: 87 },
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
spec
|
|
439
|
+
);
|
|
440
|
+
expect(minResult.valid).toBe(false);
|
|
441
|
+
expect(minResult.errors[0].field).toBe("scores[0].score");
|
|
442
|
+
expect(minResult.errors[0].message).toContain("at least 0");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it("should validate required fields in array items from schema", () => {
|
|
446
|
+
const spec: Forma = {
|
|
447
|
+
version: "1.0",
|
|
448
|
+
meta: { id: "test", title: "Test" },
|
|
449
|
+
schema: {
|
|
450
|
+
type: "object",
|
|
451
|
+
properties: {
|
|
452
|
+
people: {
|
|
453
|
+
type: "array",
|
|
454
|
+
items: {
|
|
455
|
+
type: "object",
|
|
456
|
+
properties: {
|
|
457
|
+
name: { type: "string" },
|
|
458
|
+
age: { type: "integer" },
|
|
459
|
+
},
|
|
460
|
+
required: ["name"],
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
fields: {
|
|
466
|
+
people: { label: "People", type: "array" },
|
|
467
|
+
},
|
|
468
|
+
fieldOrder: ["people"],
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// Valid - name is present (age is optional)
|
|
472
|
+
expect(
|
|
473
|
+
validate({ people: [{ name: "Alice" }, { name: "Bob", age: 30 }] }, spec).valid
|
|
474
|
+
).toBe(true);
|
|
475
|
+
|
|
476
|
+
// Invalid - missing required name
|
|
477
|
+
const result = validate({ people: [{ age: 25 }] }, spec);
|
|
478
|
+
expect(result.valid).toBe(false);
|
|
479
|
+
expect(result.errors[0].field).toBe("people[0].name");
|
|
480
|
+
expect(result.errors[0].message).toContain("required");
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("should validate nested string constraints in array items", () => {
|
|
484
|
+
const spec: Forma = {
|
|
485
|
+
version: "1.0",
|
|
486
|
+
meta: { id: "test", title: "Test" },
|
|
487
|
+
schema: {
|
|
488
|
+
type: "object",
|
|
489
|
+
properties: {
|
|
490
|
+
emails: {
|
|
491
|
+
type: "array",
|
|
492
|
+
items: {
|
|
493
|
+
type: "object",
|
|
494
|
+
properties: {
|
|
495
|
+
address: { type: "string", format: "email" },
|
|
496
|
+
label: { type: "string", minLength: 1, maxLength: 20 },
|
|
497
|
+
},
|
|
498
|
+
required: ["address"],
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
fields: {
|
|
504
|
+
emails: { label: "Emails", type: "array" },
|
|
505
|
+
},
|
|
506
|
+
fieldOrder: ["emails"],
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// Valid
|
|
510
|
+
expect(
|
|
511
|
+
validate({ emails: [{ address: "test@example.com", label: "Work" }] }, spec).valid
|
|
512
|
+
).toBe(true);
|
|
513
|
+
|
|
514
|
+
// Invalid email format
|
|
515
|
+
const emailResult = validate({ emails: [{ address: "not-an-email" }] }, spec);
|
|
516
|
+
expect(emailResult.valid).toBe(false);
|
|
517
|
+
expect(emailResult.errors[0].field).toBe("emails[0].address");
|
|
518
|
+
expect(emailResult.errors[0].message).toContain("valid email");
|
|
519
|
+
|
|
520
|
+
// Invalid label (too long)
|
|
521
|
+
const labelResult = validate(
|
|
522
|
+
{ emails: [{ address: "test@example.com", label: "This label is way too long for the constraint" }] },
|
|
523
|
+
spec
|
|
524
|
+
);
|
|
525
|
+
expect(labelResult.valid).toBe(false);
|
|
526
|
+
expect(labelResult.errors[0].field).toBe("emails[0].label");
|
|
527
|
+
expect(labelResult.errors[0].message).toContain("no more than 20");
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("should validate multipleOf in array item fields", () => {
|
|
531
|
+
const spec: Forma = {
|
|
532
|
+
version: "1.0",
|
|
533
|
+
meta: { id: "test", title: "Test" },
|
|
534
|
+
schema: {
|
|
535
|
+
type: "object",
|
|
536
|
+
properties: {
|
|
537
|
+
prices: {
|
|
538
|
+
type: "array",
|
|
539
|
+
items: {
|
|
540
|
+
type: "object",
|
|
541
|
+
properties: {
|
|
542
|
+
amount: { type: "number", multipleOf: 0.01 },
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
fields: {
|
|
549
|
+
prices: { label: "Prices", type: "array" },
|
|
550
|
+
},
|
|
551
|
+
fieldOrder: ["prices"],
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
// Valid - correct precision
|
|
555
|
+
expect(validate({ prices: [{ amount: 10.99 }, { amount: 5.0 }] }, spec).valid).toBe(true);
|
|
556
|
+
|
|
557
|
+
// Invalid - wrong precision
|
|
558
|
+
const result = validate({ prices: [{ amount: 10.999 }] }, spec);
|
|
559
|
+
expect(result.valid).toBe(false);
|
|
560
|
+
expect(result.errors[0].field).toBe("prices[0].amount");
|
|
561
|
+
expect(result.errors[0].message).toContain("multiple of 0.01");
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
describe("combined fieldDef and schema validation", () => {
|
|
566
|
+
it("should use itemFields for custom validations while using schema for type constraints", () => {
|
|
567
|
+
const spec: Forma = {
|
|
568
|
+
version: "1.0",
|
|
569
|
+
meta: { id: "test", title: "Test" },
|
|
570
|
+
schema: {
|
|
571
|
+
type: "object",
|
|
572
|
+
properties: {
|
|
573
|
+
orders: {
|
|
574
|
+
type: "array",
|
|
575
|
+
items: {
|
|
576
|
+
type: "object",
|
|
577
|
+
properties: {
|
|
578
|
+
quantity: { type: "integer", minimum: 1 },
|
|
579
|
+
price: { type: "number", minimum: 0 },
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
minItems: 1,
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
fields: {
|
|
587
|
+
orders: {
|
|
588
|
+
label: "Orders",
|
|
589
|
+
type: "array",
|
|
590
|
+
itemFields: {
|
|
591
|
+
quantity: {
|
|
592
|
+
label: "Quantity",
|
|
593
|
+
validations: [
|
|
594
|
+
{ rule: "value <= 100", message: "Cannot order more than 100 items" },
|
|
595
|
+
],
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
fieldOrder: ["orders"],
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
// Valid
|
|
604
|
+
expect(validate({ orders: [{ quantity: 5, price: 10.0 }] }, spec).valid).toBe(true);
|
|
605
|
+
|
|
606
|
+
// Invalid - schema minimum violated
|
|
607
|
+
const minResult = validate({ orders: [{ quantity: 0, price: 10.0 }] }, spec);
|
|
608
|
+
expect(minResult.valid).toBe(false);
|
|
609
|
+
expect(minResult.errors[0].message).toContain("at least 1");
|
|
610
|
+
|
|
611
|
+
// Invalid - custom FEEL validation violated
|
|
612
|
+
const customResult = validate({ orders: [{ quantity: 150, price: 10.0 }] }, spec);
|
|
613
|
+
expect(customResult.valid).toBe(false);
|
|
614
|
+
expect(customResult.errors.some((e) => e.message.includes("more than 100"))).toBe(true);
|
|
615
|
+
|
|
616
|
+
// Invalid - empty array (minItems from schema)
|
|
617
|
+
const emptyResult = validate({ orders: [] }, spec);
|
|
618
|
+
expect(emptyResult.valid).toBe(false);
|
|
619
|
+
expect(emptyResult.errors[0].message).toContain("at least 1 items");
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
});
|
|
278
623
|
});
|
package/src/engine/validate.ts
CHANGED
|
@@ -173,11 +173,12 @@ function validateField(
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
// 4. Array validation
|
|
176
|
-
if (Array.isArray(value)
|
|
176
|
+
if (Array.isArray(value)) {
|
|
177
177
|
const arrayErrors = validateArray(
|
|
178
178
|
path,
|
|
179
179
|
value,
|
|
180
180
|
fieldDef,
|
|
181
|
+
schemaProperty,
|
|
181
182
|
spec,
|
|
182
183
|
data,
|
|
183
184
|
computed,
|
|
@@ -514,6 +515,7 @@ function validateArray(
|
|
|
514
515
|
path: string,
|
|
515
516
|
value: unknown[],
|
|
516
517
|
fieldDef: FieldDefinition,
|
|
518
|
+
schemaProperty: JSONSchemaProperty | undefined,
|
|
517
519
|
spec: Forma,
|
|
518
520
|
data: Record<string, unknown>,
|
|
519
521
|
computed: Record<string, unknown>,
|
|
@@ -523,25 +525,34 @@ function validateArray(
|
|
|
523
525
|
const errors: FieldError[] = [];
|
|
524
526
|
const label = fieldDef.label ?? path;
|
|
525
527
|
|
|
526
|
-
//
|
|
527
|
-
|
|
528
|
+
// Get array schema for minItems/maxItems fallback
|
|
529
|
+
const arraySchema = schemaProperty?.type === "array" ? schemaProperty : undefined;
|
|
530
|
+
|
|
531
|
+
// Check min/max items - fieldDef overrides schema
|
|
532
|
+
const minItems = fieldDef.minItems ?? arraySchema?.minItems;
|
|
533
|
+
const maxItems = fieldDef.maxItems ?? arraySchema?.maxItems;
|
|
534
|
+
|
|
535
|
+
if (minItems !== undefined && value.length < minItems) {
|
|
528
536
|
errors.push({
|
|
529
537
|
field: path,
|
|
530
|
-
message: `${label} must have at least ${
|
|
538
|
+
message: `${label} must have at least ${minItems} items`,
|
|
531
539
|
severity: "error",
|
|
532
540
|
});
|
|
533
541
|
}
|
|
534
542
|
|
|
535
|
-
if (
|
|
543
|
+
if (maxItems !== undefined && value.length > maxItems) {
|
|
536
544
|
errors.push({
|
|
537
545
|
field: path,
|
|
538
|
-
message: `${label} must have no more than ${
|
|
546
|
+
message: `${label} must have no more than ${maxItems} items`,
|
|
539
547
|
severity: "error",
|
|
540
548
|
});
|
|
541
549
|
}
|
|
542
550
|
|
|
551
|
+
// Get item schema for nested validation
|
|
552
|
+
const itemSchema = arraySchema?.items;
|
|
553
|
+
|
|
543
554
|
// Validate each item's fields
|
|
544
|
-
if (fieldDef.itemFields) {
|
|
555
|
+
if (fieldDef.itemFields || itemSchema) {
|
|
545
556
|
for (let i = 0; i < value.length; i++) {
|
|
546
557
|
const item = value[i] as Record<string, unknown>;
|
|
547
558
|
const itemErrors = validateArrayItem(
|
|
@@ -549,6 +560,7 @@ function validateArray(
|
|
|
549
560
|
i,
|
|
550
561
|
item,
|
|
551
562
|
fieldDef.itemFields,
|
|
563
|
+
itemSchema,
|
|
552
564
|
spec,
|
|
553
565
|
data,
|
|
554
566
|
computed,
|
|
@@ -569,7 +581,8 @@ function validateArrayItem(
|
|
|
569
581
|
arrayPath: string,
|
|
570
582
|
index: number,
|
|
571
583
|
item: Record<string, unknown>,
|
|
572
|
-
itemFields: Record<string, FieldDefinition
|
|
584
|
+
itemFields: Record<string, FieldDefinition> | undefined,
|
|
585
|
+
itemSchema: JSONSchemaProperty | undefined,
|
|
573
586
|
spec: Forma,
|
|
574
587
|
data: Record<string, unknown>,
|
|
575
588
|
computed: Record<string, unknown>,
|
|
@@ -578,7 +591,20 @@ function validateArrayItem(
|
|
|
578
591
|
): FieldError[] {
|
|
579
592
|
const errors: FieldError[] = [];
|
|
580
593
|
|
|
581
|
-
|
|
594
|
+
// Get object schema for item if available
|
|
595
|
+
const objectSchema = itemSchema?.type === "object" ? itemSchema : undefined;
|
|
596
|
+
const schemaProperties = objectSchema?.properties ?? {};
|
|
597
|
+
const schemaRequired = new Set(objectSchema?.required ?? []);
|
|
598
|
+
|
|
599
|
+
// Determine which fields to validate - union of itemFields and schema properties
|
|
600
|
+
const allFieldNames = new Set([
|
|
601
|
+
...Object.keys(itemFields ?? {}),
|
|
602
|
+
...Object.keys(schemaProperties),
|
|
603
|
+
]);
|
|
604
|
+
|
|
605
|
+
for (const fieldName of allFieldNames) {
|
|
606
|
+
const fieldDef = itemFields?.[fieldName];
|
|
607
|
+
const fieldSchema = schemaProperties[fieldName];
|
|
582
608
|
const itemFieldPath = `${arrayPath}[${index}].${fieldName}`;
|
|
583
609
|
|
|
584
610
|
// Skip hidden fields
|
|
@@ -596,23 +622,36 @@ function validateArrayItem(
|
|
|
596
622
|
value,
|
|
597
623
|
};
|
|
598
624
|
|
|
599
|
-
// Required check
|
|
600
|
-
const isRequired = fieldDef
|
|
625
|
+
// Required check - fieldDef.requiredWhen overrides schema.required
|
|
626
|
+
const isRequired = fieldDef?.requiredWhen
|
|
601
627
|
? evaluateBoolean(fieldDef.requiredWhen, context)
|
|
602
|
-
:
|
|
628
|
+
: schemaRequired.has(fieldName);
|
|
603
629
|
|
|
604
630
|
if (isRequired && isEmpty(value)) {
|
|
605
631
|
errors.push({
|
|
606
632
|
field: itemFieldPath,
|
|
607
|
-
message: fieldDef
|
|
633
|
+
message: fieldDef?.label
|
|
608
634
|
? `${fieldDef.label} is required`
|
|
609
635
|
: "This field is required",
|
|
610
636
|
severity: "error",
|
|
611
637
|
});
|
|
612
638
|
}
|
|
613
639
|
|
|
614
|
-
//
|
|
615
|
-
if (
|
|
640
|
+
// Type validation from schema (only if value is present)
|
|
641
|
+
if (!isEmpty(value) && fieldSchema) {
|
|
642
|
+
const typeError = validateType(
|
|
643
|
+
itemFieldPath,
|
|
644
|
+
value,
|
|
645
|
+
fieldSchema,
|
|
646
|
+
fieldDef ?? { label: fieldName }
|
|
647
|
+
);
|
|
648
|
+
if (typeError) {
|
|
649
|
+
errors.push(typeError);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Custom validations from fieldDef
|
|
654
|
+
if (fieldDef?.validations && !isEmpty(value)) {
|
|
616
655
|
const customErrors = validateCustomRules(itemFieldPath, fieldDef.validations, context);
|
|
617
656
|
errors.push(...customErrors);
|
|
618
657
|
}
|