import { failure, Result } from '../result'; import { RuntypeBase, Static, create, Codec, mapValidationPlaceholder, assertRuntype, innerGuard, createGuardVisitedState, } from '../runtype'; import show from '../show'; import { Never } from './never'; export interface ParsedValue, TParsed> extends Codec { readonly tag: 'parsed'; readonly underlying: TUnderlying; readonly config: ParsedValueConfig; } export interface ParsedValueConfig, TParsed> { name?: string; parse: (value: Static) => Result; serialize?: (value: TParsed) => Result>; test?: RuntypeBase; } export function ParsedValue, TParsed>( underlying: TUnderlying, config: ParsedValueConfig, ): ParsedValue { assertRuntype(underlying); return create>( 'parsed', { p: (value, _innerValidate, innerValidateToPlaceholder) => { return mapValidationPlaceholder( innerValidateToPlaceholder(underlying, value), source => config.parse(source), config.test, ); }, t(value, internalTest, _sealed, isOptionalTest) { return config.test ? internalTest(config.test, value) : isOptionalTest ? undefined : failure( `${config.name || `ParsedValue<${show(underlying)}>`} does not support Runtype.test`, ); }, s(value, _internalSerialize, internalSerializeToPlaceholder, _getFields, sealed) { if (!config.serialize) { return failure( `${ config.name || `ParsedValue<${show(underlying)}>` } does not support Runtype.serialize`, ); } const testResult = config.test ? innerGuard(config.test, value, createGuardVisitedState(), sealed, true) : undefined; if (testResult) { return testResult; } const serialized = config.serialize(value); if (!serialized.success) { return serialized; } return internalSerializeToPlaceholder(underlying, serialized.value, false); }, u(mode) { switch (mode) { case 'p': return underlying; case 't': return config.test; case 's': return config.serialize ? config.test : Never; } }, }, { underlying, config, show() { return config.name || `ParsedValue<${show(underlying, false)}>`; }, }, ); }