UNPKG

typia

Version:

Superfast runtime validators with only one line

1,618 lines (1,570 loc) 49 kB
import ts from "typescript"; import { ExpressionFactory } from "../factories/ExpressionFactory"; import { IdentifierFactory } from "../factories/IdentifierFactory"; import { MetadataCollection } from "../factories/MetadataCollection"; import { MetadataFactory } from "../factories/MetadataFactory"; import { StatementFactory } from "../factories/StatementFactory"; import { TypeFactory } from "../factories/TypeFactory"; import { ValueFactory } from "../factories/ValueFactory"; import { Metadata } from "../schemas/metadata/Metadata"; import { MetadataArray } from "../schemas/metadata/MetadataArray"; import { MetadataConstant } from "../schemas/metadata/MetadataConstant"; import { MetadataMap } from "../schemas/metadata/MetadataMap"; import { MetadataObjectType } from "../schemas/metadata/MetadataObjectType"; import { MetadataSet } from "../schemas/metadata/MetadataSet"; import { MetadataTuple } from "../schemas/metadata/MetadataTuple"; import { MetadataTupleType } from "../schemas/metadata/MetadataTupleType"; import { ITypiaContext } from "../transformers/ITypiaContext"; import { TransformerError } from "../transformers/TransformerError"; import { FeatureProgrammer } from "./FeatureProgrammer"; import { IsProgrammer } from "./IsProgrammer"; import { AtomicPredicator } from "./helpers/AtomicPredicator"; import { FunctionProgrammer } from "./helpers/FunctionProgrammer"; import { ICheckEntry } from "./helpers/ICheckEntry"; import { IExpressionEntry } from "./helpers/IExpressionEntry"; import { OptionPredicator } from "./helpers/OptionPredicator"; import { UnionExplorer } from "./helpers/UnionExplorer"; import { check_array_length } from "./internal/check_array_length"; import { check_bigint } from "./internal/check_bigint"; import { check_native } from "./internal/check_native"; import { check_number } from "./internal/check_number"; import { check_string } from "./internal/check_string"; import { check_template } from "./internal/check_template"; import { decode_union_object } from "./internal/decode_union_object"; import { postfix_of_tuple } from "./internal/postfix_of_tuple"; import { wrap_metadata_rest_tuple } from "./internal/wrap_metadata_rest_tuple"; export namespace CheckerProgrammer { export interface IConfig { prefix: string; path: boolean; trace: boolean; equals: boolean; numeric: boolean; addition?: () => ts.Statement[]; decoder?: (props: { metadata: Metadata; input: ts.Expression; explore: IExplore; }) => ts.Expression; combiner: IConfig.Combiner; atomist: (props: { entry: ICheckEntry; input: ts.Expression; explore: IExplore; }) => ts.Expression; joiner: IConfig.IJoiner; success: ts.Expression; } export namespace IConfig { export interface Combiner { (props: { explore: IExplore; logic: "and" | "or"; input: ts.Expression; binaries: IBinary[]; expected: string; }): ts.Expression; } export interface IJoiner { object(props: { input: ts.Expression; entries: IExpressionEntry<ts.Expression>[]; }): ts.Expression; array(props: { input: ts.Expression; arrow: ts.ArrowFunction; }): ts.Expression; tuple?: undefined | ((exprs: ts.Expression[]) => ts.Expression); failure(props: { input: ts.Expression; expected: string; explore?: undefined | FeatureProgrammer.IExplore; }): ts.Expression; is?(expression: ts.Expression): ts.Expression; required?(exp: ts.Expression): ts.Expression; full?: | undefined | ((props: { condition: ts.Expression; input: ts.Expression; expected: string; explore: IExplore; }) => ts.Expression); } } export type IExplore = FeatureProgrammer.IExplore; export interface IBinary { expression: ts.Expression; combined: boolean; } /* ----------------------------------------------------------- WRITERS ----------------------------------------------------------- */ export const compose = (props: { context: ITypiaContext; config: IConfig; functor: FunctionProgrammer; type: ts.Type; name: string | undefined; }): FeatureProgrammer.IComposed => FeatureProgrammer.compose({ ...props, config: configure(props), }); export const write = (props: { context: ITypiaContext; config: IConfig; functor: FunctionProgrammer; type: ts.Type; name?: string; }): ts.ArrowFunction => FeatureProgrammer.write({ config: configure(props), context: props.context, functor: props.functor, type: props.type, name: props.name, }); export const write_object_functions = (props: { context: ITypiaContext; config: IConfig; functor: FunctionProgrammer; collection: MetadataCollection; }): ts.VariableStatement[] => FeatureProgrammer.write_object_functions({ config: configure(props), context: props.context, collection: props.collection, }); export const write_union_functions = (props: { context: ITypiaContext; config: IConfig; functor: FunctionProgrammer; collection: MetadataCollection; }): ts.VariableStatement[] => FeatureProgrammer.write_union_functions({ config: configure({ context: props.context, config: { ...props.config, numeric: false, }, functor: props.functor, }), collection: props.collection, }); export const write_array_functions = (props: { context: ITypiaContext; config: IConfig; functor: FunctionProgrammer; collection: MetadataCollection; }): ts.VariableStatement[] => props.collection .arrays() .filter((a) => a.recursive) .map((type, i) => StatementFactory.constant({ name: `${props.config.prefix}a${i}`, value: ts.factory.createArrowFunction( undefined, undefined, FeatureProgrammer.parameterDeclarations({ config: props.config, type: TypeFactory.keyword("any"), input: ts.factory.createIdentifier("input"), }), TypeFactory.keyword("any"), undefined, decode_array_inline({ ...props, input: ts.factory.createIdentifier("input"), array: MetadataArray.create({ type, tags: [], }), explore: { tracable: props.config.trace, source: "function", from: "array", postfix: "", }, }), ), }), ); export const write_tuple_functions = (props: { context: ITypiaContext; config: IConfig; functor: FunctionProgrammer; collection: MetadataCollection; }): ts.VariableStatement[] => props.collection .tuples() .filter((t) => t.recursive) .map((tuple, i) => StatementFactory.constant({ name: `${props.config.prefix}t${i}`, value: ts.factory.createArrowFunction( undefined, undefined, FeatureProgrammer.parameterDeclarations({ config: props.config, type: TypeFactory.keyword("any"), input: ts.factory.createIdentifier("input"), }), TypeFactory.keyword("any"), undefined, decode_tuple_inline({ config: props.config, context: props.context, functor: props.functor, input: ts.factory.createIdentifier("input"), tuple, explore: { tracable: props.config.trace, source: "function", from: "array", postfix: "", }, }), ), }), ); const configure = (props: { context: ITypiaContext; config: IConfig; functor: FunctionProgrammer; }): FeatureProgrammer.IConfig => ({ types: { input: () => TypeFactory.keyword("any"), output: (type, name) => ts.factory.createTypePredicateNode( undefined, "input", ts.factory.createTypeReferenceNode( name ?? TypeFactory.getFullName({ checker: props.context.checker, type }), ), ), }, trace: props.config.trace, path: props.config.path, prefix: props.config.prefix, initializer: (next) => { const collection: MetadataCollection = new MetadataCollection(); const result = MetadataFactory.analyze({ checker: next.context.checker, transformer: next.context.transformer, options: { escape: false, constant: true, absorb: true, }, collection, type: next.type, }); if (result.success === false) throw TransformerError.from({ code: next.functor.method, errors: result.errors, }); return { collection, metadata: result.data, }; }, addition: props.config.addition, decoder: props.config.decoder ? (next) => props.config.decoder!(next) : (next) => decode({ context: props.context, config: props.config, functor: props.functor, input: next.input, metadata: next.metadata, explore: next.explore, }), objector: { checker: props.config.decoder ? (next) => props.config.decoder!(next) : (next) => decode({ context: props.context, config: props.config, functor: props.functor, input: next.input, metadata: next.metadata, explore: next.explore, }), decoder: (next) => decode_object({ config: props.config, functor: props.functor, input: next.input, object: next.object, explore: next.explore, }), joiner: props.config.joiner.object, unionizer: props.config.equals ? (next) => decode_union_object({ checker: (v) => decode_object({ config: props.config, functor: props.functor, object: v.object, input: v.input, explore: v.explore, }), decoder: (v) => decode_object({ config: props.config, functor: props.functor, input: v.input, object: v.object, explore: { ...v.explore, tracable: true, }, }), success: props.config.joiner.is ?? ((expr) => expr), escaper: (v) => ts.factory.createReturnStatement( props.config.joiner.failure(v), ), input: next.input, objects: next.objects, explore: next.explore, }) : (next) => props.config.combiner({ logic: "or", explore: next.explore, input: next.input, binaries: next.objects.map((object) => ({ expression: decode_object({ config: props.config, functor: props.functor, object, input: next.input, explore: next.explore, }), combined: true, })), expected: `(${next.objects.map((t) => t.name).join(" | ")})`, }), failure: (next) => ts.factory.createReturnStatement(props.config.joiner.failure(next)), is: props.config.joiner.is, required: props.config.joiner.required, full: props.config.joiner.full ? (next) => props.config.joiner.full!(next) : undefined, type: TypeFactory.keyword("boolean"), }, generator: { unions: props.config.numeric ? (collection) => FeatureProgrammer.write_union_functions({ config: configure({ ...props, config: { ...props.config, numeric: false, }, }), collection, }) : undefined, arrays: (collection) => write_array_functions({ ...props, collection, }), tuples: (collection) => write_tuple_functions({ ...props, collection, }), }, }); /* ----------------------------------------------------------- DECODERS ----------------------------------------------------------- */ export const decode = (props: { context: ITypiaContext; config: IConfig; functor: FunctionProgrammer; input: ts.Expression; metadata: Metadata; explore: IExplore; }): ts.Expression => { if (props.metadata.any) return props.config.success; const top: IBinary[] = []; const binaries: IBinary[] = []; const add = (next: { exact: boolean; left: ts.Expression; right?: ts.Expression; }) => create_add({ binaries, left: next.left, right: next.right, exact: next.exact, default: props.input, }); const getConstantValue = (value: number | string | bigint | boolean) => { if (typeof value === "string") return ts.factory.createStringLiteral(value); else if (typeof value === "bigint") return ExpressionFactory.bigint(value); return ts.factory.createIdentifier(value.toString()); }; //---- // CHECK OPTIONAL //---- // @todo -> should be elaborated const checkOptional: boolean = props.metadata.empty() || props.metadata.isUnionBucket(); // NULLABLE if (checkOptional || props.metadata.nullable) if (props.metadata.nullable) add({ exact: props.metadata.nullable, left: ValueFactory.NULL(), }); else create_add({ binaries: top, default: props.input, exact: props.metadata.nullable, left: ValueFactory.NULL(), }); // UNDEFINDABLE if (checkOptional || !props.metadata.isRequired()) if (props.metadata.isRequired()) create_add({ binaries: top, default: props.input, exact: false, left: ValueFactory.UNDEFINED(), }); else add({ exact: true, left: ValueFactory.UNDEFINED(), }); // FUNCTIONAL if (props.metadata.functions.length) if ( OptionPredicator.functional(props.context.options) || props.metadata.size() !== 1 ) add({ exact: true, left: ts.factory.createStringLiteral("function"), right: ValueFactory.TYPEOF(props.input), }); else binaries.push({ combined: false, expression: props.config.success, }); //---- // VALUES //---- // CONSTANT VALUES const constants: MetadataConstant[] = props.metadata.constants.filter((c) => AtomicPredicator.constant({ metadata: props.metadata, name: c.type, }), ); const constantLength: number = constants .map((c) => c.values.length) .reduce((a, b) => a + b, 0); if (constantLength >= 10) { const values: Array<boolean | number | string | bigint> = constants .map((c) => c.values.map((v) => v.value)) .flat(); add({ exact: true, left: ts.factory.createTrue(), right: ts.factory.createCallExpression( IdentifierFactory.access( props.functor.emplaceVariable( `${props.config.prefix}v${props.functor.increment()}`, ts.factory.createNewExpression( ts.factory.createIdentifier("Set"), undefined, [ ts.factory.createArrayLiteralExpression( values.map((v) => typeof v === "boolean" ? v === true ? ts.factory.createTrue() : ts.factory.createFalse() : typeof v === "bigint" ? ExpressionFactory.bigint(v) : typeof v === "number" ? ExpressionFactory.number(v) : ts.factory.createStringLiteral(v.toString()), ), ), ], ), ), "has", ), undefined, [props.input], ), }); } else for (const c of constants) if ( AtomicPredicator.constant({ metadata: props.metadata, name: c.type, }) ) for (const v of c.values) add({ exact: true, left: getConstantValue(v.value), }); // ATOMIC VALUES for (const atom of props.metadata.atomics) if ( AtomicPredicator.atomic({ metadata: props.metadata, name: atom.type, }) === false ) continue; else if (atom.type === "number") binaries.push({ expression: props.config.atomist({ explore: props.explore, entry: check_number({ context: props.context, numeric: props.config.numeric, atomic: atom, input: props.input, }), input: props.input, }), combined: false, }); else if (atom.type === "bigint") binaries.push({ expression: props.config.atomist({ explore: props.explore, entry: check_bigint({ context: props.context, atomic: atom, input: props.input, }), input: props.input, }), combined: false, }); else if (atom.type === "string") binaries.push({ expression: props.config.atomist({ explore: props.explore, entry: check_string({ context: props.context, atomic: atom, input: props.input, }), input: props.input, }), combined: false, }); else add({ exact: true, left: ts.factory.createStringLiteral(atom.type), right: ValueFactory.TYPEOF(props.input), }); // TEMPLATE LITERAL VALUES if (props.metadata.templates.length) if (AtomicPredicator.template(props.metadata)) binaries.push({ expression: props.config.atomist({ explore: props.explore, entry: check_template({ templates: props.metadata.templates, input: props.input, }), input: props.input, }), combined: false, }); // NATIVE CLASSES for (const native of props.metadata.natives) binaries.push({ expression: check_native({ name: native.name, input: props.input, }), combined: false, }); //---- // INSTANCES //---- interface IInstance { head: ts.Expression; body: ts.Expression | null; expected: string; } const instances: IInstance[] = []; const prepare = (next: IInstance) => instances.push(next); // SETS if (props.metadata.sets.length) { const install = (body: ts.Expression | null) => prepare({ head: check_native({ name: "Set", input: props.input, }), expected: props.metadata.sets .map((elem) => `Set<${elem.getName()}>`) .join(" | "), body, }); if (props.metadata.sets.some((elem) => elem.value.any)) install(null); else install( explore_sets({ config: props.config, context: props.context, functor: props.functor, sets: props.metadata.sets, input: props.input, explore: { ...props.explore, from: "array", }, }), ); } // MAPS if (props.metadata.maps.length) { const install = (body: ts.Expression | null) => prepare({ head: check_native({ name: "Map", input: props.input, }), expected: props.metadata.maps .map(({ key, value }) => `Map<${key}, ${value}>`) .join(" | "), body, }); if (props.metadata.maps.some((elem) => elem.key.any && elem.value.any)) install(null); else install( explore_maps({ config: props.config, context: props.context, functor: props.functor, maps: props.metadata.maps, input: props.input, explore: { ...props.explore, from: "array", }, }), ); } // ARRAYS AND TUPLES if (props.metadata.tuples.length + props.metadata.arrays.length > 0) { const install = (body: ts.Expression | null) => prepare({ head: props.config.atomist({ explore: props.explore, entry: { expected: [ ...props.metadata.tuples.map((t) => t.type.name), ...props.metadata.arrays.map((a) => a.getName()), ].join(" | "), expression: ExpressionFactory.isArray(props.input), conditions: [], }, input: props.input, }), expected: [...props.metadata.tuples, ...props.metadata.arrays] .map((elem) => elem.type.name) .join(" | "), body, }); if (props.metadata.arrays.length === 0) if (props.metadata.tuples.length === 1) install( decode_tuple({ config: props.config, context: props.context, functor: props.functor, tuple: props.metadata.tuples[0]!, input: props.input, explore: { ...props.explore, from: "array", }, }), ); // TUPLE ONLY else install( explore_tuples({ config: props.config, context: props.context, functor: props.functor, tuples: props.metadata.tuples, input: props.input, explore: { ...props.explore, from: "array", }, }), ); else if (props.metadata.arrays.some((elem) => elem.type.value.any)) install(null); else if (props.metadata.tuples.length === 0) if (props.metadata.arrays.length === 1) // ARRAY ONLY install( decode_array({ config: props.config, context: props.context, functor: props.functor, array: props.metadata.arrays[0]!, input: props.input, explore: { ...props.explore, from: "array", }, }), ); else install( explore_arrays({ config: props.config, context: props.context, functor: props.functor, arrays: props.metadata.arrays, input: props.input, explore: { ...props.explore, from: "array", }, }), ); else install( explore_arrays_and_tuples({ config: props.config, context: props.context, functor: props.functor, definitions: [...props.metadata.tuples, ...props.metadata.arrays], input: props.input, explore: props.explore, }), ); } // OBJECT if (props.metadata.objects.length > 0) prepare({ head: ExpressionFactory.isObject({ checkNull: true, checkArray: props.metadata.objects.some((obj) => obj.type.properties.every( (prop) => !prop.key.isSoleLiteral() || !prop.value.isRequired(), ), ), input: props.input, }), expected: props.metadata.objects .map((obj) => obj.type.name) .join(" | "), body: explore_objects({ config: props.config, functor: props.functor, metadata: props.metadata, input: props.input, explore: { ...props.explore, from: "object", }, }), }); if (instances.length) { const transformer = (merge: (x: ts.Expression, y: ts.Expression) => ts.Expression) => (instance: IInstance) => instance.body ? { expression: merge(instance.head, instance.body), combined: true, } : { expression: instance.head, combined: false, }; if (instances.length === 1) binaries.push( transformer((head, body) => props.config.combiner({ explore: props.explore, logic: "and", input: props.input, binaries: [head, body].map((expression) => ({ expression, combined: expression !== head, })), expected: props.metadata.getName(), }), )(instances[0]!), ); else binaries.push({ expression: props.config.combiner({ explore: props.explore, logic: "or", input: props.input, binaries: instances.map(transformer(ts.factory.createLogicalAnd)), expected: props.metadata.getName(), }), combined: true, }); } // ESCAPED CASE if (props.metadata.escaped !== null) binaries.push({ combined: false, expression: props.metadata.escaped.original.size() === 1 && props.metadata.escaped.original.natives.length === 1 ? check_native({ name: props.metadata.escaped.original.natives[0]!.name, input: props.input, }) : ts.factory.createLogicalAnd( decode({ context: props.context, config: props.config, functor: props.functor, metadata: props.metadata.escaped.original, input: props.input, explore: props.explore, }), ts.factory.createLogicalAnd( IsProgrammer.decode_to_json({ checkNull: false, input: props.input, }), decode_escaped({ config: props.config, context: props.context, functor: props.functor, metadata: props.metadata.escaped.returns, input: props.input, explore: props.explore, }), ), ), }); //---- // COMBINE CONDITIONS //---- return top.length && binaries.length ? props.config.combiner({ explore: props.explore, logic: "and", input: props.input, binaries: [ ...top, { expression: props.config.combiner({ explore: props.explore, logic: "or", input: props.input, binaries, expected: props.metadata.getName(), }), combined: true, }, ], expected: props.metadata.getName(), }) : binaries.length ? props.config.combiner({ explore: props.explore, logic: "or", input: props.input, binaries, expected: props.metadata.getName(), }) : props.config.success; }; export const decode_object = (props: { config: IConfig; functor: FunctionProgrammer; object: MetadataObjectType; input: ts.Expression; explore: IExplore; }) => { props.object.validated ||= true; return FeatureProgrammer.decode_object(props); }; const decode_array = (props: { config: IConfig; context: ITypiaContext; functor: FunctionProgrammer; array: MetadataArray; input: ts.Expression; explore: IExplore; }) => { if (props.array.type.recursive === false) return decode_array_inline(props); const arrayExplore: IExplore = { ...props.explore, source: "function", from: "array", }; return ts.factory.createLogicalOr( ts.factory.createCallExpression( ts.factory.createIdentifier( props.functor.useLocal( `${props.config.prefix}a${props.array.type.index}`, ), ), undefined, FeatureProgrammer.argumentsArray({ config: props.config, explore: { ...arrayExplore, source: "function", from: "array", }, input: props.input, }), ), props.config.joiner.failure({ input: props.input, expected: props.array.type.name, explore: arrayExplore, }), ); }; const decode_array_inline = (props: { config: IConfig; context: ITypiaContext; functor: FunctionProgrammer; array: MetadataArray; input: ts.Expression; explore: IExplore; }): ts.Expression => { const length: ICheckEntry = check_array_length({ context: props.context, array: props.array, input: props.input, }); const main: ts.Expression = FeatureProgrammer.decode_array({ config: { prefix: props.config.prefix, trace: props.config.trace, path: props.config.path, decoder: (next) => decode({ ...props, ...next, }), }, functor: props.functor, combiner: props.config.joiner.array, array: props.array, input: props.input, explore: props.explore, }); return length.expression === null && length.conditions.length === 0 ? main : ts.factory.createLogicalAnd( props.config.atomist({ explore: props.explore, input: props.input, entry: length, }), main, ); }; const decode_tuple = (props: { context: ITypiaContext; config: IConfig; functor: FunctionProgrammer; tuple: MetadataTuple; input: ts.Expression; explore: IExplore; }): ts.Expression => { if (props.tuple.type.recursive === false) return decode_tuple_inline({ ...props, tuple: props.tuple.type, }); const arrayExplore: IExplore = { ...props.explore, source: "function", from: "array", }; return ts.factory.createLogicalOr( ts.factory.createCallExpression( ts.factory.createIdentifier( props.functor.useLocal( `${props.config.prefix}t${props.tuple.type.index}`, ), ), undefined, FeatureProgrammer.argumentsArray({ config: props.config, explore: { ...arrayExplore, source: "function", }, input: props.input, }), ), props.config.joiner.failure({ input: props.input, expected: props.tuple.type.name, explore: arrayExplore, }), ); }; const decode_tuple_inline = (props: { config: IConfig; context: ITypiaContext; functor: FunctionProgrammer; tuple: MetadataTupleType; input: ts.Expression; explore: IExplore; }): ts.Expression => { const binaries: ts.Expression[] = props.tuple.elements .filter((metadata) => metadata.rest === null) .map((metadata, index) => decode({ context: props.context, config: props.config, functor: props.functor, input: ts.factory.createElementAccessExpression(props.input, index), metadata, explore: { ...props.explore, from: "array", postfix: props.explore.postfix.length ? `${postfix_of_tuple(props.explore.postfix)}[${index}]"` : `"[${index}]"`, }, }), ); const rest: ts.Expression | null = props.tuple.elements.length && props.tuple.elements.at(-1)!.rest !== null ? decode({ config: props.config, context: props.context, functor: props.functor, input: ts.factory.createCallExpression( IdentifierFactory.access(props.input, "slice"), undefined, [ExpressionFactory.number(props.tuple.elements.length - 1)], ), metadata: wrap_metadata_rest_tuple( props.tuple.elements.at(-1)!.rest!, ), explore: { ...props.explore, start: props.tuple.elements.length - 1, }, }) : null; const arrayLength = ts.factory.createPropertyAccessExpression( props.input, "length", ); return props.config.combiner({ explore: props.explore, logic: "and", input: props.input, binaries: [ ...(rest === null ? props.tuple.elements.every((t) => t.optional === false) ? [ { combined: false, expression: ts.factory.createStrictEquality( arrayLength, ExpressionFactory.number(props.tuple.elements.length), ), }, ] : [ { combined: false, expression: ts.factory.createLogicalAnd( ts.factory.createLessThanEquals( ExpressionFactory.number( props.tuple.elements.filter((t) => t.optional === false) .length, ), arrayLength, ), ts.factory.createGreaterThanEquals( ExpressionFactory.number(props.tuple.elements.length), arrayLength, ), ), }, ] : []), ...(props.config.joiner.tuple ? [ { expression: props.config.joiner.tuple(binaries), combined: true, }, ] : binaries.map((expression) => ({ expression, combined: true, }))), ...(rest !== null ? [ { expression: rest, combined: true, }, ] : []), ], expected: `[${props.tuple.elements.map((t) => t.getName()).join(", ")}]`, }); }; const decode_escaped = (props: { config: IConfig; context: ITypiaContext; functor: FunctionProgrammer; metadata: Metadata; input: ts.Expression; explore: IExplore; }): ts.Expression => ts.factory.createCallExpression( ts.factory.createParenthesizedExpression( ts.factory.createArrowFunction( undefined, undefined, [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], undefined, undefined, decode({ ...props, input: ts.factory.createIdentifier("input"), }), ), ), undefined, [ ts.factory.createCallExpression( IdentifierFactory.access(props.input, "toJSON"), undefined, [], ), ], ); /* ----------------------------------------------------------- UNION TYPE EXPLORERS ----------------------------------------------------------- */ const explore_sets = (props: { context: ITypiaContext; config: IConfig; functor: FunctionProgrammer; sets: MetadataSet[]; input: ts.Expression; explore: IExplore; }): ts.Expression => ts.factory.createCallExpression( UnionExplorer.set({ config: { checker: (v) => decode({ context: props.context, config: props.config, functor: props.functor, input: v.input, metadata: v.definition, explore: v.explore, }), decoder: (v) => decode_array({ config: props.config, context: props.context, functor: props.functor, array: v.definition, input: v.input, explore: v.explore, }), empty: props.config.success, success: props.config.success, failure: (v) => ts.factory.createReturnStatement(props.config.joiner.failure(v)), }, parameters: [], input: props.input, sets: props.sets, explore: props.explore, }), undefined, undefined, ); const explore_maps = (props: { context: ITypiaContext; config: IConfig; functor: FunctionProgrammer; input: ts.Expression; maps: MetadataMap[]; explore: IExplore; }): ts.Expression => ts.factory.createCallExpression( UnionExplorer.map({ config: { checker: (v) => ts.factory.createLogicalAnd( decode({ config: props.config, context: props.context, functor: props.functor, input: ts.factory.createElementAccessExpression(v.input, 0), metadata: v.definition[0], explore: { ...v.explore, postfix: `${v.explore.postfix}[0]`, }, }), decode({ config: props.config, context: props.context, functor: props.functor, input: ts.factory.createElementAccessExpression(v.input, 1), metadata: v.definition[1], explore: { ...v.explore, postfix: `${v.explore.postfix}[1]`, }, }), ), decoder: (v) => decode_array({ context: props.context, config: props.config, functor: props.functor, array: v.definition, input: v.input, explore: v.explore, }), empty: props.config.success, success: props.config.success, failure: (v) => ts.factory.createReturnStatement(props.config.joiner.failure(v)), }, parameters: [], input: props.input, maps: props.maps, explore: props.explore, }), undefined, undefined, ); const explore_tuples = (props: { config: IConfig; context: ITypiaContext; functor: FunctionProgrammer; tuples: MetadataTuple[]; input: ts.Expression; explore: IExplore; }): ts.Expression => explore_array_like_union_types<MetadataTuple>({ config: props.config, functor: props.functor, factory: (next) => UnionExplorer.tuple({ config: { checker: (v) => decode_tuple({ context: props.context, config: props.config, functor: props.functor, input: v.input, tuple: v.definition, explore: v.explore, }), decoder: (v) => decode_tuple({ context: props.context, config: props.config, functor: props.functor, tuple: v.definition, input: v.input, explore: v.explore, }), empty: props.config.success, success: props.config.success, failure: (v) => ts.factory.createReturnStatement(props.config.joiner.failure(v)), }, parameters: next.parameters, tuples: next.definitions, input: next.input, explore: next.explore, }), definitions: props.tuples, input: props.input, explore: props.explore, }); const explore_arrays = (props: { config: IConfig; context: ITypiaContext; functor: FunctionProgrammer; arrays: MetadataArray[]; input: ts.Expression; explore: IExplore; }): ts.Expression => explore_array_like_union_types<MetadataArray>({ config: props.config, functor: props.functor, factory: (next) => UnionExplorer.array({ config: { checker: (v) => decode({ context: props.context, config: props.config, functor: props.functor, metadata: v.definition, input: v.input, explore: v.explore, }), decoder: (v) => decode_array({ context: props.context, config: props.config, functor: props.functor, array: v.definition, input: v.input, explore: v.explore, }), empty: props.config.success, success: props.config.success, failure: (v) => ts.factory.createReturnStatement(props.config.joiner.failure(v)), }, parameters: next.parameters, arrays: next.definitions, input: next.input, explore: next.explore, }), definitions: props.arrays, input: props.input, explore: props.explore, }); const explore_arrays_and_tuples = (props: { config: IConfig; context: ITypiaContext; functor: FunctionProgrammer; definitions: Array<MetadataArray | MetadataTuple>; input: ts.Expression; explore: IExplore; }): ts.Expression => explore_array_like_union_types<MetadataArray | MetadataTuple>({ config: props.config, functor: props.functor, factory: (next) => UnionExplorer.array_or_tuple({ config: { checker: (v) => v.definition instanceof MetadataTuple ? decode_tuple({ config: props.config, context: props.context, functor: props.functor, input: v.input, tuple: v.definition, explore: v.explore, }) : props.config.atomist({ explore: v.explore, entry: { expected: props.definitions .map((elem) => elem instanceof MetadataArray ? elem.getName() : elem.type.name, ) .join(" | "), expression: decode({ functor: props.functor, context: props.context, config: props.config, metadata: v.definition, input: v.input, explore: v.explore, }), conditions: [], }, input: v.container, }), decoder: (v) => v.definition instanceof MetadataTuple ? decode_tuple({ context: props.context, config: props.config, functor: props.functor, input: v.input, tuple: v.definition, explore: v.explore, }) : decode_array({ context: props.context, config: props.config, functor: props.functor, input: v.input, array: v.definition, explore: v.explore, }), empty: props.config.success, success: props.config.success, failure: (v) => ts.factory.createReturnStatement(props.config.joiner.failure(v)), }, parameters: next.parameters, definitions: next.definitions, input: next.input, explore: next.explore, }), input: props.input, definitions: props.definitions, explore: props.explore, }); const explore_array_like_union_types = < T extends MetadataArray | MetadataTuple, >(props: { config: IConfig; functor: FunctionProgrammer; factory: (next: { parameters: ts.ParameterDeclaration[]; definitions: T[]; input: ts.Expression; explore: IExplore; }) => ts.ArrowFunction; input: ts.Expression; definitions: T[]; explore: IExplore; }): ts.Expression => { const arrow = (next: { parameters: ts.ParameterDeclaration[]; explore: IExplore; input: ts.Expression; }): ts.ArrowFunction => props.factory({ parameters: next.parameters, definitions: props.definitions, input: next.input, explore: next.explore, }); if (props.definitions.every((e) => e.type.recursive === false)) ts.factory.createCallExpression( arrow({ explore: props.explore, input: props.input, parameters: [], }), undefined, [], ); const arrayExplore: IExplore = { ...props.explore, source: "function", from: "array", }; return ts.factory.createLogicalOr( ts.factory.createCallExpression( ts.factory.createIdentifier( props.functor.emplaceUnion( props.config.prefix, props.definitions.map((e) => e.type.name).join(" | "), () => arrow({ parameters: FeatureProgrammer.parameterDeclarations({ config: props.config, type: TypeFactory.keyword("any"), input: ts.factory.createIdentifier("input"), }), explore: { ...arrayExplore, postfix: "", }, input: ts.factory.createIdentifier("input"), }), ), ), undefined, FeatureProgrammer.argumentsArray(props), ), props.config.joiner.failure({ input: props.input, expected: props.definitions.map((e) => e.type.name).join(" | "), explore: arrayExplore, }), ); }; const explore_objects = (props: { config: IConfig; functor: FunctionProgrammer; input: ts.Expression; metadata: Metadata; explore: IExplore; }) => props.metadata.objects.length === 1 ? decode_object({ config: props.config, functor: props.functor, object: props.metadata.objects[0]!.type, input: props.input, explore: props.explore, }) : ts.factory.createCallExpression( ts.factory.createIdentifier( props.functor.useLocal( `${props.config.prefix}u${props.metadata.union_index!}`, ), ), undefined, FeatureProgrammer.argumentsArray(props), ); } const create_add = (props: { binaries: CheckerProgrammer.IBinary[]; default: ts.Expression; exact: boolean; left: ts.Expression; right?: ts.Expression; }) => { const factory = props.exact ? ts.factory.createStrictEquality : ts.factory.createStrictInequality; props.binaries.push({ expression: factory(props.left, props.right ?? props.default), combined: false, }); };