typia
Version:
Superfast runtime validators with only one line
172 lines (157 loc) • 5.2 kB
text/typescript
import ts from "typescript";
import { ExpressionFactory } from "../../factories/ExpressionFactory";
import { IMetadataTypeTag } from "../../schemas/metadata/IMetadataTypeTag";
export namespace RandomRanger {
export interface IDefaults {
minimum: number;
maximum: number;
gap: number;
}
export const length =
(coalesce: (method: string) => ts.Expression) =>
(defs: IDefaults) =>
(acc: length.IAccessors) =>
(tags: IMetadataTypeTag[]): ts.Expression | undefined => {
const props = {
minimum: getter(tags)(acc.minimum),
maximum: getter(tags)(acc.maximum),
};
if (props.minimum === undefined && props.maximum === undefined)
return undefined;
if (props.maximum !== undefined && props.minimum === undefined) {
if (props.maximum <= 0) {
props.maximum = 0;
props.minimum = 0;
} else if (props.maximum < defs.gap)
props.minimum = defs.minimum === 0 ? 0 : 1;
}
props.minimum ??= defs.minimum;
props.maximum ??= defs.maximum;
if (props.maximum < props.minimum) (props.maximum as number) += defs.gap;
return ts.factory.createCallExpression(coalesce("integer"), undefined, [
ExpressionFactory.number(props.minimum),
ExpressionFactory.number(props.maximum),
]);
};
export namespace length {
export interface IAccessors {
minimum: string;
maximum: string;
}
}
export const number =
(config: number.IConfig) =>
(defs: IDefaults) =>
(tags: IMetadataTypeTag[]): ts.Expression => {
const range = {
minimum: {
value: getter(tags)("minimum") ?? getter(tags)("exclusiveMinimum"),
exclusive: getter(tags)("exclusiveMinimum") !== undefined,
},
maximum: {
value: getter(tags)("maximum") ?? getter(tags)("exclusiveMaximum"),
exclusive: getter(tags)("exclusiveMaximum") !== undefined,
},
stepper: undefined,
multiply: getter(tags)("multipleOf"),
};
//----
// MULTIPLIERS
//----
if (range.multiply !== undefined) {
const { minimum, maximum } = multiplier(defs.gap)(range)(
range.multiply,
);
return ts.factory.createMultiply(
config.transform(range.multiply),
config.setter([minimum, maximum]),
);
}
//----
// RANGE
//----
// INT
const integer = (value: number) => value === Math.floor(value);
if (config.type === "int" || config.type === "uint") {
if (range.minimum.value !== undefined) {
if (range.minimum.exclusive) {
range.minimum.exclusive = false;
if (integer(range.minimum.value)) range.minimum.value += 1;
}
range.minimum.value = Math.ceil(range.minimum.value);
}
if (range.maximum.value !== undefined) {
if (range.maximum.exclusive) {
range.maximum.exclusive = false;
if (integer(range.maximum.value)) range.maximum.value -= 1;
}
range.maximum.value = Math.floor(range.maximum.value);
}
}
// UNSIGNED INT
if (config.type === "uint") {
if (range.minimum.value === undefined) range.minimum.value = 0;
else if (range.minimum.value <= 0) {
range.minimum.value = 0;
range.minimum.exclusive = false;
}
}
const minimum =
range.minimum.value ??
(range.maximum.value !== undefined
? range.maximum.value - defs.gap
: defs.minimum);
const maximum =
range.maximum.value ??
(range.minimum.value !== undefined
? range.minimum.value + defs.gap
: defs.maximum);
return config.setter([minimum, maximum]);
};
export namespace number {
export interface IConfig {
setter: (args: number[]) => ts.Expression;
transform: (value: number) => ts.Expression;
type: "int" | "uint" | "double";
}
}
}
const getter =
(tags: IMetadataTypeTag[]) =>
(kind: string): number | undefined => {
const value: bigint | number | undefined = tags.find(
(t) =>
t.kind === kind &&
(typeof t.value === "number" || typeof t.value === "bigint"),
)?.value;
return value !== undefined ? Number(value) : undefined;
};
const multiplier = (gap: number) => (range: IRange) => (m: number) => {
const minimum: number =
range.minimum.value === undefined
? 0
: (() => {
const x: number = m * Math.ceil(range.minimum.value / m);
return range.minimum.exclusive && x === range.minimum.value
? x + m
: x;
})() / m;
const maximum: number =
range.maximum.value === undefined
? gap
: (() => {
const y: number = m * Math.floor(range.maximum.value / m);
return range.maximum.exclusive && y === range.maximum.value
? y - m
: y;
})() / m;
return { minimum, maximum };
};
interface IRange {
minimum: IScalar;
maximum: IScalar;
}
interface IScalar {
value?: undefined | number;
exclusive: boolean;
}