import { Arr, Result } from 'ts-data-forge';
import { type Type } from '../type.mjs';
import {
createAssertFn,
createCastFn,
createIsFn,
prependIndexToValidationErrors,
type ValidationError,
} from '../utils/index.mjs';
/**
* Validates each element of `a` against `elementType`, collecting any element
* errors with their index prepended. Shared by the length-constrained array
* validators.
*
* @internal Not part of the public package API.
*/
export const validateElements = (
a: readonly unknown[],
elementType: Type,
): Result => {
const errors: readonly ValidationError[] = Arr.generate(function* () {
for (const [index, el] of a.entries()) {
const res = elementType.validate(el);
if (Result.isErr(res)) {
yield* prependIndexToValidationErrors(res.value, index);
}
}
});
if (Arr.isNonEmpty(errors)) {
return Result.err(errors);
}
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
return Result.ok(a as readonly A[]);
};
/**
* Assembles a structural `Type` from the length-specific pieces
* (`typeName` / `getDefaultValue` / `validate` / `fill`). Shared internally by
* the length-constrained array validators (`fixedLengthArray` etc.) and the
* `nonEmptyArray` validator; their outer signatures narrow the structural
* result to the branded array type.
*
* @internal Not part of the public package API.
*/
export const buildType = ({
typeName,
getDefaultValue,
validate,
fill,
elementType,
}: Readonly<{
typeName: string;
getDefaultValue: () => readonly Elm[];
validate: (a: unknown) => Result;
fill: (a: unknown) => readonly Elm[];
elementType: Type;
}>): Type => {
const base: Type = {
typeName,
get defaultValue() {
return getDefaultValue();
},
fill,
prune: (a) => a.map((el) => elementType.prune(el)),
validate,
is: createIsFn(validate),
cast: createCastFn(validate),
assertIs: createAssertFn(validate),
} as const;
// The branded output type `T` (e.g. `NonEmptyArray` / `FixedLengthArray`) is
// a subtype of the structural `readonly Elm[]` the validator actually
// produces. The brand is guaranteed by the caller's `validate` / `fill` but
// cannot be proven structurally, so it is asserted here once, on behalf of
// every length-constrained array validator, instead of at each call site.
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
return base as Type;
};