UNPKG

@matatbread/typia

Version:

Superfast runtime validators with only one line

698 lines (695 loc) 29.7 kB
import ts from 'typescript'; import { ExpressionFactory } from '../../factories/ExpressionFactory.mjs'; import { IdentifierFactory } from '../../factories/IdentifierFactory.mjs'; import { JsonMetadataFactory } from '../../factories/JsonMetadataFactory.mjs'; import { StatementFactory } from '../../factories/StatementFactory.mjs'; import { TypeFactory } from '../../factories/TypeFactory.mjs'; import { ValueFactory } from '../../factories/ValueFactory.mjs'; import { Metadata } from '../../schemas/metadata/Metadata.mjs'; import { MetadataArray } from '../../schemas/metadata/MetadataArray.mjs'; import { MetadataAtomic } from '../../schemas/metadata/MetadataAtomic.mjs'; import { FeatureProgrammer } from '../FeatureProgrammer.mjs'; import { IsProgrammer } from '../IsProgrammer.mjs'; import { AtomicPredicator } from '../helpers/AtomicPredicator.mjs'; import { FunctionProgrammer } from '../helpers/FunctionProgrammer.mjs'; import { OptionPredicator } from '../helpers/OptionPredicator.mjs'; import { StringifyJoiner } from '../helpers/StringifyJoinder.mjs'; import { StringifyPredicator } from '../helpers/StringifyPredicator.mjs'; import { UnionExplorer } from '../helpers/UnionExplorer.mjs'; import { check_native } from '../internal/check_native.mjs'; import { decode_union_object } from '../internal/decode_union_object.mjs'; import { postfix_of_tuple } from '../internal/postfix_of_tuple.mjs'; import { wrap_metadata_rest_tuple } from '../internal/wrap_metadata_rest_tuple.mjs'; var JsonStringifyProgrammer; (function (JsonStringifyProgrammer) { /* ----------------------------------------------------------- WRITER ----------------------------------------------------------- */ JsonStringifyProgrammer.decompose = (props) => { const config = configure(props); if (props.validated === false) config.addition = (collection) => IsProgrammer.write_function_statements({ context: props.context, functor: props.functor, collection, }); const composed = FeatureProgrammer.compose({ ...props, config, }); return { functions: composed.functions, statements: composed.statements, arrow: ts.factory.createArrowFunction(undefined, undefined, composed.parameters, composed.response, undefined, composed.body), }; }; JsonStringifyProgrammer.write = (props) => { const functor = new FunctionProgrammer(props.modulo.getText()); const result = JsonStringifyProgrammer.decompose({ ...props, validated: false, functor, }); return FeatureProgrammer.writeDecomposed({ modulo: props.modulo, functor, result, }); }; const write_array_functions = (props) => 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({ config: props.config, functor: props.functor, input: ts.factory.createIdentifier("input"), array: MetadataArray.create({ type, tags: [], }), explore: { tracable: props.config.trace, source: "function", from: "array", postfix: "", }, })), })); const write_tuple_functions = (props) => 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({ context: props.context, config: props.config, functor: props.functor, input: ts.factory.createIdentifier("input"), tuple, explore: { tracable: props.config.trace, source: "function", from: "array", postfix: "", }, })), })); /* ----------------------------------------------------------- DECODERS ----------------------------------------------------------- */ const decode = (props) => { // ANY TYPE if (props.metadata.any === true) return wrap_required({ input: props.input, metadata: props.metadata, explore: props.explore, expression: wrap_functional({ input: props.input, metadata: props.metadata, explore: props.explore, expression: ts.factory.createCallExpression(ts.factory.createIdentifier("JSON.stringify"), undefined, [props.input]), }), }); // ONLY NULL OR UNDEFINED const size = props.metadata.size(); if (size === 0 && (props.metadata.isRequired() === false || props.metadata.nullable === true)) { if (props.metadata.isRequired() === false && props.metadata.nullable === true) return props.explore.from === "array" ? ts.factory.createStringLiteral("null") : ts.factory.createConditionalExpression(ts.factory.createStrictEquality(ts.factory.createNull(), props.input), undefined, ts.factory.createStringLiteral("null"), undefined, ts.factory.createIdentifier("undefined")); else if (props.metadata.isRequired() === false) return props.explore.from === "array" ? ts.factory.createStringLiteral("null") : ts.factory.createIdentifier("undefined"); else return ts.factory.createStringLiteral("null"); } //---- // LIST UP UNION TYPES //---- const unions = []; // toJSON() METHOD if (props.metadata.escaped !== null) unions.push({ type: "resolved", is: props.metadata.escaped.original.size() === 1 && props.metadata.escaped.original.natives[0]?.name === "Date" ? () => check_native({ name: "Date", input: props.input, }) : () => IsProgrammer.decode_to_json({ checkNull: false, input: props.input, }), value: () => decode_to_json({ ...props, metadata: props.metadata.escaped.returns, }), }); else if (props.metadata.functions.length) unions.push({ type: "functional", is: () => IsProgrammer.decode_functional(props.input), value: () => decode_functional(props.explore), }); // TEMPLATES if (props.metadata.templates.length) if (AtomicPredicator.template(props.metadata)) { const partial = Metadata.initialize(); partial.atomics.push(MetadataAtomic.create({ type: "string", tags: [] })), unions.push({ type: "template literal", is: () => IsProgrammer.decode({ ...props, metadata: partial, }), value: () => decode_atomic({ ...props, type: "string", }), }); } // CONSTANTS for (const constant of props.metadata.constants) if (AtomicPredicator.constant({ metadata: props.metadata, name: constant.type, }) === false) continue; else if (constant.type !== "string") unions.push({ type: "atomic", is: () => IsProgrammer.decode({ ...props, metadata: (() => { const partial = Metadata.initialize(); partial.atomics.push(MetadataAtomic.create({ type: constant.type, tags: [], })); return partial; })(), }), value: () => decode_atomic({ ...props, type: constant.type, }), }); else if (props.metadata.templates.length === 0) unions.push({ type: "const string", is: () => IsProgrammer.decode({ ...props, metadata: (() => { const partial = Metadata.initialize(); partial.atomics.push(MetadataAtomic.create({ type: "string", tags: [], })); return partial; })(), }), value: () => decode_constant_string({ ...props, values: [...constant.values.map((v) => v.value)], }), }); /// ATOMICS for (const a of props.metadata.atomics) if (AtomicPredicator.atomic({ metadata: props.metadata, name: a.type, })) unions.push({ type: "atomic", is: () => IsProgrammer.decode({ ...props, metadata: (() => { const partial = Metadata.initialize(); partial.atomics.push(a); return partial; })(), }), value: () => decode_atomic({ ...props, type: a.type, }), }); // TUPLES for (const tuple of props.metadata.tuples) unions.push({ type: "tuple", is: () => IsProgrammer.decode({ ...props, metadata: (() => { const partial = Metadata.initialize(); partial.tuples.push(tuple); return partial; })(), }), value: () => decode_tuple({ ...props, tuple, }), }); // ARRAYS if (props.metadata.arrays.length) { const value = props.metadata.arrays.length === 1 ? () => decode_array({ ...props, array: props.metadata.arrays[0], explore: { ...props.explore, from: "array", }, }) : props.metadata.arrays.some((elem) => elem.type.value.any) ? () => ts.factory.createCallExpression(ts.factory.createIdentifier("JSON.stringify"), undefined, [props.input]) : () => explore_arrays({ ...props, arrays: props.metadata.arrays, explore: { ...props.explore, from: "array", }, }); unions.push({ type: "array", is: () => ExpressionFactory.isArray(props.input), value, }); } // BUILT-IN CLASSES if (props.metadata.natives.length) for (const native of props.metadata.natives) unions.push({ type: "object", is: () => check_native({ name: native.name, input: props.input, }), value: () => AtomicPredicator.native(native.name) ? decode_atomic({ ...props, type: native.name.toLowerCase(), }) : ts.factory.createStringLiteral("{}"), }); // SETS if (props.metadata.sets.length) unions.push({ type: "object", is: () => ExpressionFactory.isInstanceOf("Set", props.input), value: () => ts.factory.createStringLiteral("{}"), }); // MAPS if (props.metadata.maps.length) unions.push({ type: "object", is: () => ExpressionFactory.isInstanceOf("Map", props.input), value: () => ts.factory.createStringLiteral("{}"), }); // OBJECTS if (props.metadata.objects.length) unions.push({ type: "object", is: () => ExpressionFactory.isObject({ checkNull: true, checkArray: props.metadata.objects.some((object) => object.type.properties.every((prop) => !prop.key.isSoleLiteral() || !prop.value.isRequired())), input: props.input, }), value: () => explore_objects({ ...props, explore: { ...props.explore, from: "object", }, }), }); //---- // RETURNS //---- // CHECK NULL AND UNDEFINED const wrapper = (output) => wrap_required({ input: props.input, metadata: props.metadata, explore: props.explore, expression: wrap_nullable({ input: props.input, metadata: props.metadata, expression: output, }), }); // DIRECT RETURN if (unions.length === 0) return ts.factory.createCallExpression(ts.factory.createIdentifier("JSON.stringify"), undefined, [props.input]); else if (unions.length === 1) return wrapper(unions[0].value()); // RETURN WITH TYPE CHECKING return wrapper(ts.factory.createCallExpression(ts.factory.createArrowFunction(undefined, undefined, [], undefined, undefined, iterate({ context: props.context, functor: props.functor, input: props.input, expected: props.metadata.getName(), unions, })), undefined, undefined)); }; const decode_object = (props) => FeatureProgrammer.decode_object({ config: { trace: false, path: false, prefix: PREFIX, }, functor: props.functor, object: props.object, input: props.input, explore: props.explore, }); const decode_array = (props) => props.array.type.recursive ? ts.factory.createCallExpression(ts.factory.createIdentifier(props.functor.useLocal(`${props.config.prefix}a${props.array.type.index}`)), undefined, FeatureProgrammer.argumentsArray({ config: props.config, input: props.input, explore: { ...props.explore, source: "function", from: "array", }, })) : decode_array_inline(props); const decode_array_inline = (props) => FeatureProgrammer.decode_array({ config: props.config, functor: props.functor, combiner: StringifyJoiner.array, array: props.array, input: props.input, explore: props.explore, }); const decode_tuple = (props) => props.tuple.type.recursive ? ts.factory.createCallExpression(ts.factory.createIdentifier(props.functor.useLocal(`${props.config.prefix}t${props.tuple.type.index}`)), undefined, FeatureProgrammer.argumentsArray({ config: props.config, explore: { ...props.explore, source: "function", }, input: props.input, })) : decode_tuple_inline({ ...props, tuple: props.tuple.type, }); const decode_tuple_inline = (props) => { const elements = props.tuple.elements .filter((elem) => elem.rest === null) .map((elem, index) => decode({ ...props, input: ts.factory.createElementAccessExpression(props.input, index), metadata: elem, explore: { ...props.explore, from: "array", postfix: props.explore.postfix.length ? `${postfix_of_tuple(props.explore.postfix)}[${index}]"` : `"[${index}]"`, }, })); const rest = (() => { if (props.tuple.elements.length === 0) return null; const last = props.tuple.elements.at(-1); if (last.rest === null) return null; const code = decode({ ...props, 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, }, }); return ts.factory.createCallExpression(props.context.importer.internal("jsonStringifyRest"), undefined, [code]); })(); return StringifyJoiner.tuple({ elements, rest, }); }; const decode_atomic = (props) => { if (props.type === "string") return ts.factory.createCallExpression(props.context.importer.internal("jsonStringifyString"), undefined, [props.input]); else if (props.type === "number" && OptionPredicator.numeric(props.context.options)) props = { ...props, input: ts.factory.createCallExpression(props.context.importer.internal("jsonStringifyNumber"), undefined, [props.input]), }; return props.explore.from !== "top" ? props.input : ts.factory.createCallExpression(IdentifierFactory.access(props.input, "toString"), undefined, undefined); }; const decode_constant_string = (props) => { if (props.values.every((v) => !StringifyPredicator.require_escape(v))) return [ ts.factory.createStringLiteral('"'), props.input, ts.factory.createStringLiteral('"'), ].reduce((x, y) => ts.factory.createAdd(x, y)); return decode_atomic({ ...props, type: "string", }); }; const decode_to_json = (props) => { return decode({ ...props, input: ts.factory.createCallExpression(IdentifierFactory.access(props.input, "toJSON"), undefined, []), }); }; const decode_functional = (explore) => explore.from === "array" ? ts.factory.createStringLiteral("null") : ts.factory.createIdentifier("undefined"); /* ----------------------------------------------------------- EXPLORERS ----------------------------------------------------------- */ const explore_objects = (props) => props.metadata.objects.length === 1 ? decode_object({ functor: props.functor, input: props.input, object: props.metadata.objects[0].type, explore: props.explore, }) : ts.factory.createCallExpression(ts.factory.createIdentifier(props.functor.useLocal(`${PREFIX}u${props.metadata.union_index}`)), undefined, FeatureProgrammer.argumentsArray(props)); const explore_arrays = (props) => explore_array_like_union_types({ ...props, elements: props.arrays, factory: (next) => UnionExplorer.array({ config: { checker: (v) => IsProgrammer.decode({ context: props.context, functor: props.functor, metadata: v.definition, input: v.input, explore: v.explore, }), decoder: (v) => decode_array({ config: props.config, functor: props.functor, input: v.input, array: v.definition, explore: v.explore, }), empty: ts.factory.createStringLiteral("[]"), success: ts.factory.createTrue(), failure: (v) => create_throw_error({ context: props.context, functor: props.functor, expected: v.expected, input: v.input, }), }, parameters: next.parameters, input: next.input, arrays: next.definitions, explore: next.explore, }), //(next.parameters)(next.input, next.elements, next.explore), }); const explore_array_like_union_types = (props) => { const arrow = (next) => props.factory({ definitions: props.elements, parameters: next.parameters, input: next.input, explore: next.explore, }); if (props.elements.every((e) => e.type.recursive === false)) ts.factory.createCallExpression(arrow({ parameters: [], explore: props.explore, input: props.input, }), undefined, []); const arrayExplore = { ...props.explore, source: "function", from: "array", }; return ts.factory.createCallExpression(ts.factory.createIdentifier(props.functor.emplaceUnion(props.config.prefix, props.elements.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({ config: props.config, explore: arrayExplore, input: props.input, })); }; /* ----------------------------------------------------------- RETURN SCRIPTS ----------------------------------------------------------- */ const wrap_required = (props) => { if (props.metadata.isRequired() === true && props.metadata.any === false) return props.expression; return ts.factory.createConditionalExpression(ts.factory.createStrictInequality(ts.factory.createIdentifier("undefined"), props.input), undefined, props.expression, undefined, props.explore.from === "array" ? ts.factory.createStringLiteral("null") : ts.factory.createIdentifier("undefined")); }; const wrap_nullable = (props) => { if (props.metadata.nullable === false) return props.expression; return ts.factory.createConditionalExpression(ts.factory.createStrictInequality(ts.factory.createNull(), props.input), undefined, props.expression, undefined, ts.factory.createStringLiteral("null")); }; const wrap_functional = (props) => { if (props.metadata.functions.length === 0) return props.expression; return ts.factory.createConditionalExpression(ts.factory.createStrictInequality(ts.factory.createStringLiteral("function"), ValueFactory.TYPEOF(props.input)), undefined, props.expression, undefined, decode_functional(props.explore)); }; const iterate = (props) => ts.factory.createBlock([ ...props.unions.map((u) => ts.factory.createIfStatement(u.is(), ts.factory.createReturnStatement(u.value()))), create_throw_error(props), ], true); /* ----------------------------------------------------------- CONFIGURATIONS ----------------------------------------------------------- */ const PREFIX = "_s"; const configure = (props) => { const config = { types: { input: (type, name) => ts.factory.createTypeReferenceNode(name ?? TypeFactory.getFullName({ checker: props.context.checker, type })), output: () => TypeFactory.keyword("string"), }, prefix: PREFIX, trace: false, path: false, initializer, decoder: (next) => decode({ context: props.context, functor: props.functor, config, metadata: next.metadata, input: next.input, explore: next.explore, }), objector: { checker: (next) => IsProgrammer.decode({ context: props.context, functor: props.functor, metadata: next.metadata, input: next.input, explore: next.explore, }), decoder: (next) => decode_object({ functor: props.functor, input: next.input, object: next.object, explore: next.explore, }), joiner: (next) => StringifyJoiner.object({ ...next, context: props.context, }), unionizer: (next) => decode_union_object({ checker: (v) => IsProgrammer.decode_object({ context: props.context, functor: props.functor, input: v.input, object: v.object, explore: v.explore, }), decoder: (v) => decode_object({ functor: props.functor, input: v.input, object: v.object, explore: v.explore, }), success: (exp) => exp, escaper: (v) => create_throw_error({ context: props.context, functor: props.functor, expected: v.expected, input: v.input, }), objects: next.objects, explore: next.explore, input: next.input, }), failure: (next) => create_throw_error({ context: props.context, functor: props.functor, expected: next.expected, input: next.input, }), }, generator: { arrays: (collection) => write_array_functions({ config, functor: props.functor, collection, }), tuples: (collection) => write_tuple_functions({ config, context: props.context, functor: props.functor, collection, }), }, }; return config; }; const initializer = (props) => JsonMetadataFactory.analyze({ method: props.functor.method, checker: props.context.checker, transformer: props.context.transformer, type: props.type, }); const create_throw_error = (props) => ts.factory.createExpressionStatement(ts.factory.createCallExpression(props.context.importer.internal("throwTypeGuardError"), [], [ ts.factory.createObjectLiteralExpression([ ts.factory.createPropertyAssignment("method", ts.factory.createStringLiteral(props.functor.method)), ts.factory.createPropertyAssignment("expected", ts.factory.createStringLiteral(props.expected)), ts.factory.createPropertyAssignment("value", props.input), ], true), ])); })(JsonStringifyProgrammer || (JsonStringifyProgrammer = {})); export { JsonStringifyProgrammer }; //# sourceMappingURL=JsonStringifyProgrammer.mjs.map