@udraft/core
Version:
uDraft is a language and stack agnostic code-generation tool that simplifies full-stack development by converting a single YAML file into code for rapid development.
220 lines (199 loc) • 6.61 kB
text/typescript
import { UModel } from "../entities/model";
import { URenderer } from "../entities/renderer";
import { addPackageJsonDependency } from "../helpers/package";
import { $attr } from "../shortcuts/queries";
import { RenderContent, RenderPath, RenderSelection } from "../types/renderer";
import { writeToCursor } from "../utils/rendering";
import { UModule } from "../entities/module";
import TSClassRenderer from "./ts-class-renderer";
import {
_array,
_in,
_max,
_maxLength,
_min,
_minLength,
_notEmpty,
_notIn,
_required,
_size,
_regex,
_enum,
_ref,
} from "../shortcuts/attributes";
const KEYS = {
packageJson: "packageJson",
};
export default class TSClassValidatorRenderer extends URenderer {
private _classRenderer!: TSClassRenderer;
private _where?: (module: UModule, model: UModel) => boolean;
private _updatePackageJson = false;
constructor(options?: {
where?: (module: UModule, model: UModel) => boolean;
updatePackageJson?: boolean;
}) {
super("ts@class-validators");
if (options?.where) this._where = options.where;
if (options?.updatePackageJson)
this._updatePackageJson = options.updatePackageJson;
}
async select(): Promise<RenderSelection> {
this._classRenderer = this.$draft().$requireRenderer<TSClassRenderer>(
this,
"ts@classes"
);
const models = this.$models(this._where).filter(
(model) => !!this._classRenderer.$output(this._classRenderer.$key(model))
);
const paths: RenderPath[] = [...this._classRenderer.$paths(models)];
if (this._updatePackageJson)
paths.push({
key: KEYS.packageJson,
path: "package.json",
});
return {
paths,
models,
};
}
async render(): Promise<RenderContent[]> {
const output: RenderContent[] = [];
if (this._updatePackageJson)
output.push({
key: KEYS.packageJson,
content: addPackageJsonDependency(
this.$content("packageJson")!.content,
[
{
name: "class-validator",
version: "^0.14.1",
},
]
),
});
const models = this.$selection().models || [];
models.forEach((model) => {
if (!!$attr(model, _enum())) return;
const modelKey = this._classRenderer.$key(model);
let content = this.$content(modelKey)?.content || "";
if (!content) return;
const fields = model.$fields();
const importedValidators: string[] = [];
const importValidator = (validator?: string) => {
if (!validator) return;
if (!importedValidators.includes(validator))
importedValidators.push(validator);
};
for (let field of fields) {
const fieldCursor = ` ${this._classRenderer.$fieldSignature(
field
)};\n`;
const attributes = [
{
attr: _required(),
value: (val: boolean) =>
val === true ? `@IsDefined()` : `@IsOptional()`,
},
{
attr: _min(),
value: (val: number) => `@Min(${val})`,
},
{
attr: _max(),
value: (val: number) => `@Max(${val})`,
},
{
attr: _minLength(),
value: (val: number) => `@MinLength(${val})`,
},
{
attr: _maxLength(),
value: (val: number) => `@MaxLength(${val})`,
},
{
attr: _notEmpty(),
value: (val: boolean) => (val === true ? `@IsNotEmpty()` : ""),
},
{
attr: _size(),
value: (val: number) => `@Length(${val})`,
},
{
attr: _in(),
value: (val: any[]) => `@IsIn(${JSON.stringify(val)})`,
},
{
attr: _notIn(),
value: (val: any[]) => `@IsNotIn(${JSON.stringify(val)})`,
},
{
attr: _array(),
value: (val: boolean) => (val === true ? `@IsArray()` : ""),
},
{
attr: _regex(),
value: (val: RegExp) => `@Matches(${val.toString()})`,
},
];
for (let attribute of attributes) {
// if (attribute.attr.$name() == "array") debugger;
const value = $attr<any>(field, attribute.attr);
if (value !== null) {
const decorator = (attribute.value as any)(value);
if (decorator) {
importValidator(decorator.match(/@(\w+)/)[1]);
content = writeToCursor(fieldCursor, ` ${decorator}\n`, content);
}
}
}
let decorator = "";
let validationOptions = "";
if ($attr(field, _array()) === true) validationOptions = "{each: true}";
if (field.$type() === "string")
decorator = `@IsString(${validationOptions})`;
else if (field.$type() === "number")
decorator = `@IsNumber(${
validationOptions ? "{}, " + validationOptions : ""
})`;
else if (field.$type() === "boolean")
decorator = `@IsBoolean(${validationOptions})`;
else if (field.$type() === "date")
decorator = `@IsDate(${validationOptions})`;
else if (field.$type() === "int")
decorator = `@IsInt(${validationOptions})`;
else if (field.$type() === "float")
decorator = `@IsNumber(${
validationOptions ? "{}, " + validationOptions : ""
})`;
else if (field.$type() === "nested") {
const nestedModel = $attr(field, _ref());
if (nestedModel) {
if ($attr(nestedModel, _enum())) {
decorator = `@IsEnum(${this._classRenderer.$className(
nestedModel
)}${validationOptions ? ", " + validationOptions : ""})`;
} else decorator = `@ValidateNested(${validationOptions})`;
}
}
if (decorator) {
importValidator(decorator.match(/@(\w+)/)?.[1]);
content = writeToCursor(fieldCursor, ` ${decorator}\n`, content);
}
if (!content.includes(fieldCursor + "}"))
content = content.replace(fieldCursor, fieldCursor + `\n`);
}
content =
`import {\n${importedValidators
.map((v) => " " + v + ",\n")
.join("")
.replace(/,\n$/, "\n")}} from "class-validator";\n` +
(!content.match("^import") ? "\n" : "") +
content;
output.push({
key: modelKey,
content,
});
});
return output;
}
}