UNPKG

n4s

Version:

typed schema validation version of enforce

101 lines (88 loc) 3.06 kB
/* eslint-disable max-nested-callbacks */ import { lengthEquals, mapFirst } from 'vest-utils'; import { ctx } from '../../enforceContext'; import { transformResult } from '../../ruleResult'; import { RuleRunReturn } from '../../utils/RuleRunReturn'; /** * Validates that a value is an array and all elements match at least one of the provided rules. * Each array element must pass at least one of the validation rules. * * @template T - The element type of the array * @param value - The array to validate * @param rules - One or more RuleInstances that elements should match * @returns RuleRunReturn indicating success or failure * * @example * ```typescript * // Eager API - array of strings * enforce(['a', 'b', 'c']) * .isArrayOf(enforce.isString()); // passes * * enforce([1, 2, 'three']) * .isArrayOf(enforce.isString()); // fails * * // Lazy API - array of numbers or strings * const mixedArrayRule = enforce.isArrayOf( * enforce.isNumber(), * enforce.isString() * ); * * mixedArrayRule.test([1, 'two', 3, 'four']); // true * mixedArrayRule.test([1, 2, true]); // false (boolean not allowed) * * // Complex schema validation * const usersRule = enforce.isArrayOf( * enforce.shape({ * name: enforce.isString(), * age: enforce.isNumber() * }) * ); * * usersRule.test([ * { name: 'John', age: 30 }, * { name: 'Jane', age: 25 } * ]); // true * ``` */ export function isArrayOf<T>(value: T[], ...rules: any[]): RuleRunReturn<T[]> { if (!Array.isArray(value)) { return RuleRunReturn.Failing(value); } const parsedArray: any[] = []; const failingResult = mapFirst(value, (item, breakout, index) => { const res = ctx.run({ value: item, set: true, meta: { index } }, () => { let lastRes: RuleRunReturn<any> | undefined; let passingTransformedType: any = item; // Try each rule with the item - any rule passing is OK const anyPass = rules.some(rule => { const rawResult = rule.run(item); lastRes = rawResult; const transformed = transformResult(rawResult, 'isArrayOf', item); if (transformed.pass) { passingTransformedType = rawResult.type ?? item; } return transformed.pass; }); if (anyPass) { parsedArray.push(passingTransformedType); return RuleRunReturn.Passing(passingTransformedType); } // If failed and we have a single rule, return its failure (might contain nested path) if (lengthEquals(rules, 1) && lastRes) { return lastRes; } return RuleRunReturn.Failing(item); }); if (!res.pass) { const currentPath = res.path || []; const newRes = { ...res, path: [index.toString(), ...currentPath] }; breakout(true, newRes); } }); return failingResult || RuleRunReturn.Passing(parsedArray as T[]); } // Type for isArrayOf rule instance - should chain array rules like isArray does export type IsArrayOfRuleInstance< T, TInput = T, > = import('../arrayRules').ArrayRuleInstance<T, TInput>;