import type { ConfigValidationIssue } from "../schema/issues"; import type { JsonValue } from "../types"; import { OperatorKeys } from "../schema/fragments"; import { issue, joinPath } from "../schema/issues"; const OPERATOR_KEY_SET = new Set(OperatorKeys); export function isJsonValue(value: unknown): value is JsonValue { if (value === null) return true; if (typeof value === "string" || typeof value === "boolean") return true; if (typeof value === "number") return Number.isFinite(value); if (Array.isArray(value)) return value.every(isJsonValue); if (typeof value === "object") { return Object.values(value as Record).every(isJsonValue); } return false; } export function isPlainRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } export function validateOperatorObject( operators: unknown, path: string, targetName?: string, options: { requireAtLeastOne: boolean } = { requireAtLeastOne: true }, ): ConfigValidationIssue[] { if (!isPlainRecord(operators)) return [issue("invalid-type", path, "必须为操作符对象", targetName)]; const issues: ConfigValidationIssue[] = []; let found = 0; for (const [key, value] of Object.entries(operators)) { if (!OPERATOR_KEY_SET.has(key)) { issues.push(issue("unknown-operator", joinPath(path, key), "是未知 operator", targetName)); continue; } if (value === undefined) continue; found++; issues.push(...validateOperatorValue(key, value, joinPath(path, key), targetName)); } if (options.requireAtLeastOne && found === 0) { issues.push(issue("empty-operator", path, "必须包含至少一个合法 operator", targetName)); } return issues; } export function validateOperatorValue( key: string, value: unknown, path: string, targetName?: string, ): ConfigValidationIssue[] { switch (key) { case "contains": return typeof value === "string" ? [] : [issue("invalid-type", path, "必须为字符串", targetName)]; case "empty": case "exists": return typeof value === "boolean" ? [] : [issue("invalid-type", path, "必须为布尔值", targetName)]; case "equals": return isJsonValue(value) ? [] : [issue("invalid-type", path, "必须为 JSON value", targetName)]; case "gt": case "gte": case "lt": case "lte": return typeof value === "number" && Number.isFinite(value) ? [] : [issue("invalid-type", path, "必须为有限数字", targetName)]; case "match": if (typeof value !== "string") return [issue("invalid-type", path, "必须为字符串", targetName)]; try { new RegExp(value); return []; } catch { return [issue("invalid-regex", path, "正则不合法", targetName)]; } default: return [issue("unknown-operator", path, "是未知 operator", targetName)]; } }