typia
Version:
Superfast runtime validators with only one line
383 lines (371 loc) • 13.1 kB
text/typescript
import ts from "typescript";
import { ExpressionFactory } from "../factories/ExpressionFactory";
import { IdentifierFactory } from "../factories/IdentifierFactory";
import { StatementFactory } from "../factories/StatementFactory";
import { TypeFactory } from "../factories/TypeFactory";
import { IProject } from "../transformers/IProject";
import { CheckerProgrammer } from "./CheckerProgrammer";
import { FeatureProgrammer } from "./FeatureProgrammer";
import { IsProgrammer } from "./IsProgrammer";
import { FunctionImporter } from "./helpers/FunctionImporter";
import { OptionPredicator } from "./helpers/OptionPredicator";
import { check_everything } from "./internal/check_everything";
import { check_object } from "./internal/check_object";
export namespace ValidateProgrammer {
export const decompose = (props: {
project: IProject;
modulo: ts.LeftHandSideExpression;
importer: FunctionImporter;
equals: boolean;
type: ts.Type;
name: string | undefined;
}): FeatureProgrammer.IDecomposed => {
const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose(props);
const composed: FeatureProgrammer.IComposed = CheckerProgrammer.compose({
...props,
config: {
prefix: "$v",
path: true,
trace: true,
numeric: OptionPredicator.numeric(props.project.options),
equals: props.equals,
atomist: (explore) => (entry) => (input) =>
[
...(entry.expression ? [entry.expression] : []),
...(entry.conditions.length === 0
? []
: entry.conditions.length === 1
? entry.conditions[0]!.map((cond) =>
ts.factory.createLogicalOr(
cond.expression,
create_report_call(
explore.from === "top"
? ts.factory.createTrue()
: ts.factory.createIdentifier("_exceptionable"),
)(
ts.factory.createIdentifier(
explore.postfix
? `_path + ${explore.postfix}`
: "_path",
),
cond.expected,
input,
),
),
)
: [
ts.factory.createLogicalOr(
entry.conditions
.map((set) =>
set
.map((s) => s.expression)
.reduce((a, b) =>
ts.factory.createLogicalAnd(a, b),
),
)
.reduce((a, b) => ts.factory.createLogicalOr(a, b)),
create_report_call(
explore.from === "top"
? ts.factory.createTrue()
: ts.factory.createIdentifier("_exceptionable"),
)(
ts.factory.createIdentifier(
explore.postfix
? `_path + ${explore.postfix}`
: "_path",
),
entry.expected,
input,
),
),
]),
].reduce((x, y) => ts.factory.createLogicalAnd(x, y)),
combiner: combine(props.equals)(props.project)(props.importer),
joiner: joiner(props.equals)(props.project)(props.importer),
success: ts.factory.createTrue(),
},
});
const arrow: ts.ArrowFunction = ts.factory.createArrowFunction(
undefined,
undefined,
[IdentifierFactory.parameter("input", TypeFactory.keyword("any"))],
ts.factory.createTypeReferenceNode(
`typia.IValidation<${
props.name ??
TypeFactory.getFullName(props.project.checker)(props.type)
}>`,
),
undefined,
ts.factory.createBlock(
[
// validate when false === is<T>(input)
ts.factory.createIfStatement(
ts.factory.createStrictEquality(
ts.factory.createFalse(),
ts.factory.createCallExpression(
ts.factory.createIdentifier("__is"),
undefined,
[ts.factory.createIdentifier("input")],
),
),
ts.factory.createBlock([
// prepare errors
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("errors"),
ts.factory.createToken(ts.SyntaxKind.EqualsToken),
ts.factory.createArrayLiteralExpression([]),
),
),
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("$report"),
ts.factory.createToken(ts.SyntaxKind.EqualsToken),
ts.factory.createCallExpression(
IdentifierFactory.access(
ts.factory.createParenthesizedExpression(
ts.factory.createAsExpression(
props.modulo,
TypeFactory.keyword("any"),
),
),
)("report"),
[],
[ts.factory.createIdentifier("errors")],
),
),
),
ts.factory.createExpressionStatement(
ts.factory.createCallExpression(
ts.factory.createArrowFunction(
undefined,
undefined,
composed.parameters,
undefined,
undefined,
composed.body,
),
undefined,
[
ts.factory.createIdentifier("input"),
ts.factory.createStringLiteral("$input"),
ts.factory.createTrue(),
],
),
),
StatementFactory.constant(
"success",
ts.factory.createStrictEquality(
ExpressionFactory.number(0),
ts.factory.createIdentifier("errors.length"),
),
),
ts.factory.createReturnStatement(
ts.factory.createAsExpression(
create_output(),
TypeFactory.keyword("any"),
),
),
]),
),
ts.factory.createReturnStatement(
ts.factory.createAsExpression(
ts.factory.createObjectLiteralExpression(
[
ts.factory.createPropertyAssignment(
"success",
ts.factory.createTrue(),
),
ts.factory.createPropertyAssignment(
"errors",
ts.factory.createArrayLiteralExpression([]),
),
ts.factory.createPropertyAssignment(
"data",
ts.factory.createIdentifier("input"),
),
],
true,
),
TypeFactory.keyword("any"),
),
),
],
true,
),
);
return {
functions: {
...is.functions,
...composed.functions,
},
statements: [
...is.statements,
...composed.statements,
StatementFactory.constant("__is", is.arrow),
StatementFactory.mut("errors"),
StatementFactory.mut("$report"),
],
arrow,
};
};
export const write =
(project: IProject) =>
(modulo: ts.LeftHandSideExpression) =>
(equals: boolean) =>
(type: ts.Type, name?: string) => {
const importer: FunctionImporter = new FunctionImporter(modulo.getText());
const result: FeatureProgrammer.IDecomposed = decompose({
equals,
project,
modulo,
importer,
type,
name,
});
return FeatureProgrammer.writeDecomposed({
modulo,
importer,
result,
});
};
}
const combine =
(equals: boolean) =>
(project: IProject) =>
(importer: FunctionImporter): CheckerProgrammer.IConfig.Combiner =>
(explore: CheckerProgrammer.IExplore) => {
if (explore.tracable === false)
return IsProgrammer.configure({
object: validate_object(equals)(project)(importer),
numeric: true,
})(project)(importer).combiner(explore);
const path: string = explore.postfix
? `_path + ${explore.postfix}`
: "_path";
return (logic) => (input, binaries, expected) =>
logic === "and"
? binaries
.map((binary) =>
binary.combined
? binary.expression
: ts.factory.createLogicalOr(
binary.expression,
create_report_call(
explore.source === "top"
? ts.factory.createTrue()
: ts.factory.createIdentifier("_exceptionable"),
)(ts.factory.createIdentifier(path), expected, input),
),
)
.reduce(ts.factory.createLogicalAnd)
: ts.factory.createLogicalOr(
binaries
.map((binary) => binary.expression)
.reduce(ts.factory.createLogicalOr),
create_report_call(
explore.source === "top"
? ts.factory.createTrue()
: ts.factory.createIdentifier("_exceptionable"),
)(ts.factory.createIdentifier(path), expected, input),
);
};
const validate_object =
(equals: boolean) => (project: IProject) => (importer: FunctionImporter) =>
check_object({
equals,
undefined: true,
assert: false,
reduce: ts.factory.createLogicalAnd,
positive: ts.factory.createTrue(),
superfluous: (value) =>
create_report_call()(
ts.factory.createAdd(
ts.factory.createIdentifier("_path"),
ts.factory.createCallExpression(importer.use("join"), undefined, [
ts.factory.createIdentifier("key"),
]),
),
"undefined",
value,
),
halt: (expr) =>
ts.factory.createLogicalOr(
ts.factory.createStrictEquality(
ts.factory.createFalse(),
ts.factory.createIdentifier("_exceptionable"),
),
expr,
),
})(project)(importer);
const joiner =
(equals: boolean) =>
(project: IProject) =>
(importer: FunctionImporter): CheckerProgrammer.IConfig.IJoiner => ({
object: validate_object(equals)(project)(importer),
array: (input, arrow) =>
check_everything(
ts.factory.createCallExpression(
IdentifierFactory.access(input)("map"),
undefined,
[arrow],
),
),
failure: (value, expected, explore) =>
create_report_call(
explore?.from === "top"
? ts.factory.createTrue()
: ts.factory.createIdentifier("_exceptionable"),
)(
ts.factory.createIdentifier(
explore?.postfix ? `_path + ${explore.postfix}` : "_path",
),
expected,
value,
),
tuple: (binaries) =>
check_everything(ts.factory.createArrayLiteralExpression(binaries, true)),
});
const create_output = () =>
ts.factory.createObjectLiteralExpression(
[
ts.factory.createShorthandPropertyAssignment("success"),
ts.factory.createShorthandPropertyAssignment("errors"),
ts.factory.createPropertyAssignment(
"data",
ts.factory.createConditionalExpression(
ts.factory.createIdentifier("success"),
undefined,
ts.factory.createIdentifier("input"),
undefined,
ts.factory.createIdentifier("undefined"),
),
),
],
true,
);
const create_report_call =
(exceptionable?: ts.Expression) =>
(
path: ts.Expression,
expected: string,
value: ts.Expression,
): ts.Expression =>
ts.factory.createCallExpression(
ts.factory.createIdentifier("$report"),
undefined,
[
exceptionable ?? ts.factory.createIdentifier("_exceptionable"),
ts.factory.createObjectLiteralExpression(
[
ts.factory.createPropertyAssignment("path", path),
ts.factory.createPropertyAssignment(
"expected",
ts.factory.createStringLiteral(expected),
),
ts.factory.createPropertyAssignment("value", value),
],
true,
),
],
);