/** * 字段 DSL 解析器 * * DSL 语法: * 字段名:类型[修饰符] 标签 "示例值" [验证规则...] * * 示例: * title:string 标题 "示例标题" min:2 max:100 * email:string! 邮箱 "test@example.com" email * description:string? 描述 "描述内容" max:500 * price:number 价格 "99.99" min:0 * status:enum(draft,published) 状态 "draft" */ export type FieldType = | 'string' | 'number' | 'boolean' | 'date' | 'datetime' | 'enum'; export interface Validation { type: 'min' | 'max' | 'email' | 'url' | 'pattern'; value?: string | number; } export interface FieldDefinition { name: string; // 字段名 type: FieldType; // 字段类型 label: string; // 中文标签 example: string; // 示例值 nullable: boolean; // 是否可空 unique: boolean; // 是否唯一 validations: Validation[]; // 验证规则 options?: string[]; // 枚举选项 flags: { noCreate: boolean; // 不在创建时使用 noUpdate: boolean; // 不在更新时使用 noTable: boolean; // 不在表格中显示 }; } // 匹配: 字段名:类型[修饰符] 标签 "示例值" [验证规则...] const FIELD_REGEX = /^(\w+):(\w+(?:\([^)]+\))?)([\?!]*)\s+(.+?)\s+"([^"]+)"(?:\s+(.+))?$/; /** * 解析字段 DSL 字符串 */ export function parseFields(dsl: string): FieldDefinition[] { const lines = dsl .split('\n') .map((line) => line.trim()) .filter((line) => line && !line.startsWith('#')); return lines.map((line) => parseFieldLine(line)); } /** * 解析单行字段定义 */ function parseFieldLine(line: string): FieldDefinition { const match = line.match(FIELD_REGEX); if (!match) { throw new Error(`无效的字段定义: ${line}`); } const [, name, typeStr, modifiers, label, example, validationsStr] = match; // 解析类型 let type: FieldType; let options: string[] | undefined; if (typeStr.startsWith('enum(')) { type = 'enum'; options = typeStr .slice(5, -1) .split(',') .map((s) => s.trim()); } else { type = typeStr as FieldType; } // 解析修饰符 const nullable = modifiers.includes('?'); const unique = modifiers.includes('!'); // 解析验证规则和标志 const validations: Validation[] = []; const flags = { noCreate: false, noUpdate: false, noTable: false }; if (validationsStr) { const parts = validationsStr.split(/\s+/); for (const part of parts) { if (part === 'noCreate') { flags.noCreate = true; } else if (part === 'noUpdate') { flags.noUpdate = true; } else if (part === 'noTable') { flags.noTable = true; } else if (part.startsWith('min:')) { validations.push({ type: 'min', value: parseFloat(part.slice(4)) }); } else if (part.startsWith('max:')) { validations.push({ type: 'max', value: parseFloat(part.slice(4)) }); } else if (part === 'email') { validations.push({ type: 'email' }); } else if (part === 'url') { validations.push({ type: 'url' }); } else if (part.startsWith('pattern:')) { validations.push({ type: 'pattern', value: part.slice(8) }); } } } return { name, type, label, example, nullable, unique, validations, options, flags, }; } /** * 获取 TypeScript 类型 */ export function getTsType(field: FieldDefinition): string { const typeMap: Record = { string: 'string', number: 'number', boolean: 'boolean', date: 'Date', datetime: 'Date', enum: field.options?.map((o) => `'${o}'`).join(' | ') || 'string', }; return typeMap[field.type] || 'string'; } /** * 获取 TypeScript 响应类型(Date 转 string) */ export function getTsResponseType(field: FieldDefinition): string { if (field.type === 'date' || field.type === 'datetime') { return 'string'; } return getTsType(field); } /** * 获取 Prisma 类型 */ export function getPrismaType(field: FieldDefinition): string { const typeMap: Record = { string: 'String', number: 'Float', boolean: 'Boolean', date: 'DateTime', datetime: 'DateTime', enum: 'String', }; const baseType = typeMap[field.type] || 'String'; return field.nullable ? `${baseType}?` : baseType; } /** * 获取验证装饰器列表 */ export function getValidationDecorators(field: FieldDefinition): string[] { const decorators: string[] = []; // 类型验证 switch (field.type) { case 'string': decorators.push('@IsString()'); break; case 'number': decorators.push('@IsNumber()'); break; case 'boolean': decorators.push('@IsBoolean()'); break; case 'date': case 'datetime': decorators.push('@IsDate()'); decorators.push('@Type(() => Date)'); break; case 'enum': decorators.push( `@IsIn([${field.options?.map((o) => `'${o}'`).join(', ')}])`, ); break; } // 验证规则 for (const v of field.validations) { switch (v.type) { case 'min': if (field.type === 'string') { decorators.push(`@MinLength(${v.value})`); } else { decorators.push(`@Min(${v.value})`); } break; case 'max': if (field.type === 'string') { decorators.push(`@MaxLength(${v.value})`); } else { decorators.push(`@Max(${v.value})`); } break; case 'email': decorators.push('@IsEmail()'); break; case 'url': decorators.push('@IsUrl()'); break; case 'pattern': decorators.push(`@Matches(${v.value})`); break; } } return decorators; } /** * 获取验证器导入列表 */ export function getValidationImports(fields: FieldDefinition[]): string[] { const imports = new Set(); for (const field of fields) { switch (field.type) { case 'string': imports.add('IsString'); break; case 'number': imports.add('IsNumber'); break; case 'boolean': imports.add('IsBoolean'); break; case 'date': case 'datetime': imports.add('IsDate'); break; case 'enum': imports.add('IsIn'); break; } for (const v of field.validations) { switch (v.type) { case 'min': imports.add(field.type === 'string' ? 'MinLength' : 'Min'); break; case 'max': imports.add(field.type === 'string' ? 'MaxLength' : 'Max'); break; case 'email': imports.add('IsEmail'); break; case 'url': imports.add('IsUrl'); break; case 'pattern': imports.add('Matches'); break; } } if (field.nullable) { imports.add('IsOptional'); } } return Array.from(imports); } /** * 获取 Zod 验证字符串 */ export function getZodValidation(field: FieldDefinition): string { let zod = ''; switch (field.type) { case 'string': zod = 'z.string()'; for (const v of field.validations) { if (v.type === 'min') zod += `.min(${v.value}, '最少 ${v.value} 个字符')`; if (v.type === 'max') zod += `.max(${v.value}, '最多 ${v.value} 个字符')`; if (v.type === 'email') zod += `.email('请输入有效的邮箱地址')`; if (v.type === 'url') zod += `.url('请输入有效的 URL')`; } break; case 'number': zod = 'z.coerce.number()'; for (const v of field.validations) { if (v.type === 'min') zod += `.min(${v.value}, '最小值为 ${v.value}')`; if (v.type === 'max') zod += `.max(${v.value}, '最大值为 ${v.value}')`; } break; case 'boolean': zod = 'z.boolean()'; break; case 'enum': zod = `z.enum([${field.options?.map((o) => `'${o}'`).join(', ')}])`; break; default: zod = 'z.string()'; } if (field.nullable) { zod += '.optional()'; } return zod; } /** * 生成表单控件代码 */ export function getFormControl(field: FieldDefinition): string { switch (field.type) { case 'string': { const maxLen = field.validations.find((v) => v.type === 'max')?.value; if (maxLen && Number(maxLen) > 100) { return `