104 lines
3.6 KiB
TypeScript
104 lines
3.6 KiB
TypeScript
|
|
import { type ParseError } from 'inline-schema';
|
||
|
|
import type { Command, CommandSchema } from './types.js';
|
||
|
|
import { parseCommand } from './command-parse.js';
|
||
|
|
import { parseCommandSchema } from './schema-parse.js';
|
||
|
|
|
||
|
|
export function validateCommand(
|
||
|
|
command: Command,
|
||
|
|
schema: CommandSchema
|
||
|
|
): { valid: true } | { valid: false; errors: string[] } {
|
||
|
|
const errors = validateCommandCore(command, schema);
|
||
|
|
|
||
|
|
if (errors.length > 0) {
|
||
|
|
return { valid: false, errors };
|
||
|
|
}
|
||
|
|
|
||
|
|
return { valid: true };
|
||
|
|
}
|
||
|
|
|
||
|
|
function validateCommandCore(command: Command, schema: CommandSchema): string[] {
|
||
|
|
const errors: string[] = [];
|
||
|
|
|
||
|
|
if (command.name !== schema.name) {
|
||
|
|
errors.push(`命令名称不匹配:期望 "${schema.name}",实际 "${command.name}"`);
|
||
|
|
}
|
||
|
|
|
||
|
|
const requiredParams = schema.params.filter(p => p.required);
|
||
|
|
const variadicParam = schema.params.find(p => p.variadic);
|
||
|
|
|
||
|
|
if (command.params.length < requiredParams.length) {
|
||
|
|
errors.push(`参数不足:至少需要 ${requiredParams.length} 个参数,实际 ${command.params.length} 个`);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!variadicParam && command.params.length > schema.params.length) {
|
||
|
|
errors.push(`参数过多:最多 ${schema.params.length} 个参数,实际 ${command.params.length} 个`);
|
||
|
|
}
|
||
|
|
|
||
|
|
const requiredOptions = schema.options.filter(o => o.required);
|
||
|
|
for (const opt of requiredOptions) {
|
||
|
|
const hasOption = opt.name in command.options || (opt.short && opt.short in command.options);
|
||
|
|
if (!hasOption) {
|
||
|
|
errors.push(`缺少必需选项:--${opt.name}${opt.short ? ` 或 -${opt.short}` : ''}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return errors;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function parseCommandWithSchema(
|
||
|
|
input: string,
|
||
|
|
schemaStr: string
|
||
|
|
): { command: Command; valid: true } | { command: Command; valid: false; errors: string[] } {
|
||
|
|
const schema = parseCommandSchema(schemaStr);
|
||
|
|
const command = parseCommand(input);
|
||
|
|
|
||
|
|
const errors = validateCommandCore(command, schema);
|
||
|
|
if (errors.length > 0) {
|
||
|
|
return { command, valid: false, errors };
|
||
|
|
}
|
||
|
|
|
||
|
|
const parseErrors: string[] = [];
|
||
|
|
|
||
|
|
const parsedParams: unknown[] = [];
|
||
|
|
for (let i = 0; i < command.params.length; i++) {
|
||
|
|
const paramValue = command.params[i];
|
||
|
|
const paramSchema = schema.params[i]?.schema;
|
||
|
|
|
||
|
|
if (paramSchema) {
|
||
|
|
try {
|
||
|
|
const parsed = typeof paramValue === 'string'
|
||
|
|
? paramSchema.parse(paramValue)
|
||
|
|
: paramValue;
|
||
|
|
parsedParams.push(parsed);
|
||
|
|
} catch (e) {
|
||
|
|
const err = e as ParseError;
|
||
|
|
parseErrors.push(`参数 "${schema.params[i]?.name}" 解析失败:${err.message}`);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
parsedParams.push(paramValue);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const parsedOptions: Record<string, unknown> = { ...command.options };
|
||
|
|
for (const [key, value] of Object.entries(command.options)) {
|
||
|
|
const optSchema = schema.options.find(o => o.name === key || o.short === key);
|
||
|
|
if (optSchema?.schema && typeof value === 'string') {
|
||
|
|
try {
|
||
|
|
parsedOptions[key] = optSchema.schema.parse(value);
|
||
|
|
} catch (e) {
|
||
|
|
const err = e as ParseError;
|
||
|
|
parseErrors.push(`选项 "--${key}" 解析失败:${err.message}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (parseErrors.length > 0) {
|
||
|
|
return { command: { ...command, params: parsedParams, options: parsedOptions }, valid: false, errors: parseErrors };
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
command: { ...command, params: parsedParams, options: parsedOptions },
|
||
|
|
valid: true,
|
||
|
|
};
|
||
|
|
}
|