import { expected, failure, Failure, FullError, typesAreNotCompatible, unableToAssign, } from '../result'; import { create, RuntypeBase, Codec, createValidationPlaceholder, assertRuntype } from '../runtype'; import show from '../show'; export type StaticTuple[]> = { [key in keyof TElements]: TElements[key] extends RuntypeBase ? E : unknown; }; export type ReadonlyStaticTuple[]> = { readonly [key in keyof TElements]: TElements[key] extends RuntypeBase ? E : unknown; }; export interface Tuple< TElements extends readonly RuntypeBase[] = readonly RuntypeBase[], > extends Codec> { readonly tag: 'tuple'; readonly components: TElements; readonly isReadonly: false; } export interface ReadonlyTuple< TElements extends readonly RuntypeBase[] = readonly RuntypeBase[], > extends Codec> { readonly tag: 'tuple'; readonly components: TElements; readonly isReadonly: true; } export function isTupleRuntype(runtype: RuntypeBase): runtype is Tuple { return 'tag' in runtype && (runtype as Tuple).tag === 'tuple'; } /** * Construct a tuple runtype from runtypes for each of its elements. */ export function Tuple< T extends readonly [RuntypeBase, ...RuntypeBase[]] | readonly [], >(...components: T): Tuple { assertRuntype(...components); const result = create>( 'tuple', (x, innerValidate, _innerValidateToPlaceholder, _getFields, sealed) => { if (!Array.isArray(x)) { return expected(`tuple to be an array`, x); } if (x.length !== components.length) { return expected(`an array of length ${components.length}`, x.length); } return createValidationPlaceholder([...x] as any, placeholder => { let fullError: FullError | undefined = undefined; let firstError: Failure | undefined; for (let i = 0; i < components.length; i++) { let validatedComponent = innerValidate( components[i], x[i], sealed && sealed.deep ? { deep: true } : false, ); if (!validatedComponent.success) { if (!fullError) { fullError = unableToAssign(x, result); } fullError.push(typesAreNotCompatible(`[${i}]`, validatedComponent)); firstError = firstError || failure(validatedComponent.message, { key: validatedComponent.key ? `[${i}].${validatedComponent.key}` : `[${i}]`, fullError: fullError, }); } else { placeholder[i] = validatedComponent.value; } } return firstError; }); }, { components, isReadonly: false, show() { return `${this.isReadonly ? `readonly ` : ``}[${( components as readonly RuntypeBase[] ) .map(e => show(e, false)) .join(', ')}]`; }, }, ); return result; } export function ReadonlyTuple< T extends readonly [RuntypeBase, ...RuntypeBase[]] | readonly [], >(...components: T): ReadonlyTuple { const tuple: any = Tuple(...components); tuple.isReadonly = true; return tuple; }