// ============================================================ // validators.ts — env.yaml 配置类型定义与运行时校验 // ============================================================ // 职责: // 1. 定义 env.yaml 的 TypeScript 接口 // 2. 提供运行时校验函数(零依赖,手动实现) // 3. 为 config.ts 提供类型安全的配置读取 // // 使用方式: // import { validateConfig, type EnvConfig } from "./db/validators"; // const raw = validateConfig(parsedYaml); // ============================================================ /** env.yaml 顶层结构 */ export interface EnvConfig { db: DbConfig; redis: RedisConfig; exchange: ExchangeConfig; logging: LoggingConfig; } export interface DbConfig { host: string; port: number; name: string; user: string; password: string; } export interface RedisConfig { url: string; publish_enabled: boolean; } /** 交易所 API 密钥配置(按交易所 ID 索引) */ export interface ExchangeConfig { /** 现货市场 API Key */ binance: ExchangeApiKeys; /** USDT-M 永续合约 API Key */ binance_futures: ExchangeApiKeys; // 未来扩展:okx、bybit 等 [exchangeId: string]: ExchangeApiKeys | undefined; } export interface ExchangeApiKeys { api_key: string; api_secret: string; } export interface LoggingConfig { level: "trace" | "debug" | "info" | "warn" | "error" | "fatal"; node_env: "development" | "production" | "test"; } // ============================================================ // 运行时校验(零依赖) // ============================================================ const VALID_LOG_LEVELS = ["trace", "debug", "info", "warn", "error", "fatal"] as const; const VALID_NODE_ENVS = ["development", "production", "test"] as const; /** * 校验并返回类型安全的配置对象。 * 校验失败时抛出明确错误信息,遵循 fail-fast 原则。 */ export function validateConfig(raw: unknown): EnvConfig { if (typeof raw !== "object" || raw === null) { throw new Error(`[config] env.yaml 顶层必须为 object,实际: ${typeof raw}`); } const obj = raw as Record; // --- db --- const db = obj["db"]; if (typeof db !== "object" || db === null) { throw new Error("[config] env.yaml 缺少 db 配置段"); } const dbObj = db as Record; const dbHost = assertString(dbObj["host"], "db.host"); const dbPort = assertPort(dbObj["port"], "db.port"); const dbName = assertString(dbObj["name"], "db.name"); const dbUser = assertString(dbObj["user"], "db.user"); const dbPassword = assertString(dbObj["password"], "db.password"); // --- redis --- const redis = obj["redis"]; if (typeof redis !== "object" || redis === null) { throw new Error("[config] env.yaml 缺少 redis 配置段"); } const redisObj = redis as Record; const redisUrl = assertString(redisObj["url"], "redis.url"); const redisPublishEnabled = assertBoolean(redisObj["publish_enabled"], "redis.publish_enabled"); // --- exchange --- const exchange = obj["exchange"]; if (typeof exchange !== "object" || exchange === null) { throw new Error("[config] env.yaml 缺少 exchange 配置段"); } const exObj = exchange as Record; const binance = exObj["binance"]; if (typeof binance !== "object" || binance === null) { throw new Error("[config] env.yaml exchange 缺少 binance 配置"); } const binanceObj = binance as Record; const binanceApiKey = assertString(binanceObj["api_key"], "exchange.binance.api_key"); const binanceApiSecret = assertString(binanceObj["api_secret"], "exchange.binance.api_secret"); // --- binance_futures --- const binanceFutures = exObj["binance_futures"]; if (typeof binanceFutures !== "object" || binanceFutures === null) { throw new Error("[config] env.yaml exchange 缺少 binance_futures 配置"); } const futuresObj = binanceFutures as Record; const futuresApiKey = assertString(futuresObj["api_key"], "exchange.binance_futures.api_key"); const futuresApiSecret = assertString(futuresObj["api_secret"], "exchange.binance_futures.api_secret"); // --- logging --- const logging = obj["logging"]; if (typeof logging !== "object" || logging === null) { throw new Error("[config] env.yaml 缺少 logging 配置段"); } const logObj = logging as Record; const logLevel = assertEnum(logObj["level"], VALID_LOG_LEVELS, "logging.level"); const nodeEnv = assertEnum(logObj["node_env"], VALID_NODE_ENVS, "logging.node_env"); return { db: { host: dbHost, port: dbPort, name: dbName, user: dbUser, password: dbPassword, }, redis: { url: redisUrl, publish_enabled: redisPublishEnabled, }, exchange: { binance: { api_key: binanceApiKey, api_secret: binanceApiSecret, }, binance_futures: { api_key: futuresApiKey, api_secret: futuresApiSecret, }, }, logging: { level: logLevel, node_env: nodeEnv, }, }; } // ============================================================ // 辅助校验函数 // ============================================================ function assertString(value: unknown, path: string): string { if (typeof value !== "string" || value.trim() === "") { throw new Error(`[config] ${path} 必须为非空字符串,实际: ${JSON.stringify(value)}`); } return value; } function assertPort(value: unknown, path: string): number { if (typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535) { return value; } if (typeof value === "string" && /^\d+$/.test(value)) { const n = parseInt(value, 10); if (n > 0 && n <= 65535) return n; } throw new Error(`[config] ${path} 必须为有效端口号 (1-65535),实际: ${JSON.stringify(value)}`); } function assertBoolean(value: unknown, path: string): boolean { if (typeof value === "boolean") return value; if (value === "true" || value === "false") return value === "true"; throw new Error(`[config] ${path} 必须为 boolean,实际: ${JSON.stringify(value)}`); } function assertEnum( value: unknown, allowed: T, path: string, ): T[number] { const s = String(value); if ((allowed as readonly string[]).includes(s)) { return s as T[number]; } throw new Error( `[config] ${path} 必须为 ${allowed.join(" | ")} 之一,实际: ${JSON.stringify(value)}`, ); }