将 type/configKey 从 "command" 统一为 "cmd",源码目录 runner/command/ → runner/cmd/, spec 目录 command-checker/ → cmd-checker/,测试全部改用 bun -e 替代 Unix 系统命令, 归档 cmd-checker-enhancement 变更并同步 delta spec 到主 spec。
309 lines
18 KiB
Markdown
309 lines
18 KiB
Markdown
## Purpose
|
||
|
||
定义 HTTP 拨测工具的 YAML 配置文件格式、解析校验规则和 CLI 启动流程。
|
||
|
||
## Requirements
|
||
|
||
### Requirement: YAML 配置文件格式
|
||
系统 SHALL 支持通过 YAML 配置文件定义全部运行参数,包括 server 配置、runtime 配置、checker 默认值和 typed target 列表(含可选 group 字段)。target MUST 使用 `type` 字段声明 checker 类型,HTTP 领域字段 MUST 放在 `http` 分组,cmd 领域字段 MUST 放在 `cmd` 分组。HTTP target 的 `http` 分组 SHALL 支持可选的 `ignoreSSL`(布尔值)和 `maxRedirects`(非负整数)字段。
|
||
|
||
#### Scenario: 完整配置文件解析
|
||
- **WHEN** 系统启动并读取包含 server、runtime、defaults、targets(含 group 字段)的 YAML 配置文件
|
||
- **THEN** 系统 SHALL 正确解析所有字段并用于初始化服务、调度引擎和对应 checker runner
|
||
|
||
#### Scenario: 最简 HTTP 配置文件解析
|
||
- **WHEN** 系统读取只包含一个 `type: http` target 和 `http.url` 的 YAML 配置文件(省略 server、runtime、defaults 和 expect)
|
||
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段(host=127.0.0.1, port=3000, dir=./data, interval=30s, timeout=10s, runtime.maxConcurrentChecks=20, http.method=GET, http.maxBodyBytes=100MB, http.ignoreSSL=false, http.maxRedirects=0, group="default")
|
||
|
||
#### Scenario: 最简 cmd 配置文件解析
|
||
- **WHEN** 系统读取只包含一个 `type: cmd` target 和 `cmd.exec` 的 YAML 配置文件
|
||
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段(interval=30s, timeout=10s, cmd.cwd 为配置文件所在目录, cmd.maxOutputBytes=100MB)
|
||
|
||
#### Scenario: per-target 配置覆盖全局默认值
|
||
- **WHEN** 某个 target 指定 interval、timeout 或对应领域分组中的默认字段
|
||
- **THEN** 该 target SHALL 使用其自身的值,不受 defaults 中对应字段影响
|
||
|
||
#### Scenario: HTTP target 配置 ignoreSSL
|
||
- **WHEN** YAML 配置中 HTTP target 设置 `http.ignoreSSL: true`
|
||
- **THEN** 系统 SHALL 解析该字段并在执行时跳过 SSL 证书校验
|
||
|
||
#### Scenario: HTTP target 配置 maxRedirects
|
||
- **WHEN** YAML 配置中 HTTP target 设置 `http.maxRedirects: 5`
|
||
- **THEN** 系统 SHALL 解析该字段并在执行时允许最多跟随 5 次重定向
|
||
|
||
### Requirement: CLI 参数
|
||
系统 SHALL 通过单一命令行参数接受 YAML 配置文件路径。
|
||
|
||
#### Scenario: 指定配置文件启动
|
||
- **WHEN** 用户执行 `./dial-server ./probes.yaml`
|
||
- **THEN** 系统 SHALL 读取并解析指定路径的 YAML 文件作为配置
|
||
|
||
#### Scenario: 未提供配置文件路径
|
||
- **WHEN** 用户启动程序时未提供任何命令行参数
|
||
- **THEN** 系统 SHALL 以错误退出并提示需要指定配置文件路径
|
||
|
||
#### Scenario: 配置文件不存在
|
||
- **WHEN** 用户指定的配置文件路径不存在
|
||
- **THEN** 系统 SHALL 以错误退出并提示文件不存在
|
||
|
||
### Requirement: 配置校验
|
||
系统 SHALL 在启动时对 YAML 配置进行完整校验,校验失败时以非零状态退出并输出清晰的错误信息。系统 SHALL 使用 TypeBox 定义配置契约和 raw config TypeScript 类型,由 Ajv 校验 TypeBox 生成的 JSON Schema,再执行启动期语义 validator。配置加载流程 SHALL 明确区分 `RawProbeConfig`、`ValidatedProbeConfig`、`ResolvedConfig` 三段生命周期。JSON Schema 契约 SHALL 覆盖业务无关的结构规则,包括字段类型、必填字段、枚举、数组与对象形状、数值范围和未知字段。语义 validator SHALL 覆盖契约不适合表达的业务规则,包括 target name 唯一性、checker type 注册状态、时长和大小解析、HTTP URL、正则可编译、JSONPath 子集和 XPath 可编译。
|
||
|
||
契约校验和语义 validator SHALL 统一产出 `ConfigValidationIssue`,最终由配置加载流程统一渲染为中文错误信息。
|
||
|
||
系统 SHALL 导出完整 `probe-config.schema.json`,该文件 SHALL 与运行期 TypeBox fragments 生成的 JSON Schema 保持一致,用于用户配置引用和编辑器提示。
|
||
|
||
除 `headers`、`env` 等明确声明为动态键值表的对象外,配置中的未知字段 SHALL 导致启动期配置错误。系统 MUST NOT 静默忽略未知字段。
|
||
|
||
#### Scenario: target 缺少必填字段
|
||
- **WHEN** YAML 中某个 target 缺少 name 或 type 字段
|
||
- **THEN** 系统 SHALL 以错误退出,提示哪个 target 缺少哪个字段
|
||
|
||
#### Scenario: HTTP target 缺少 url
|
||
- **WHEN** YAML 中某个 target 配置 `type: http` 但缺少 `http.url`
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 缺少 http.url 字段
|
||
|
||
#### Scenario: cmd target 缺少 exec
|
||
- **WHEN** YAML 中某个 target 配置 `type: cmd` 但缺少 `cmd.exec`
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 缺少 cmd.exec 字段
|
||
|
||
#### Scenario: target type 非法
|
||
- **WHEN** YAML 中某个 target 的 type 不是已注册 checker 类型
|
||
- **THEN** 系统 SHALL 以错误退出,提示不支持的 target type 和当前支持的 type 列表
|
||
|
||
#### Scenario: target name 重复
|
||
- **WHEN** YAML 中存在两个 name 相同的 target
|
||
- **THEN** 系统 SHALL 以错误退出,提示重复的 name
|
||
|
||
#### Scenario: group 字段类型校验
|
||
- **WHEN** YAML 中某个 target 的 `group` 字段不是字符串
|
||
- **THEN** 系统 SHALL 以错误退出并提示 group 字段类型错误
|
||
|
||
#### Scenario: interval 格式非法
|
||
- **WHEN** interval 或 timeout 值不是有效的时长格式(如 `30s`、`5m`、`500ms`)
|
||
- **THEN** 系统 SHALL 以错误退出并提示格式错误
|
||
|
||
#### Scenario: maxConcurrentChecks 非法
|
||
- **WHEN** runtime.maxConcurrentChecks 不是正整数
|
||
- **THEN** 系统 SHALL 以错误退出并提示 runtime.maxConcurrentChecks 格式错误
|
||
|
||
#### Scenario: interval 或 timeout 解析结果非法
|
||
- **WHEN** interval 或 timeout 解析结果不是正整数毫秒(如 `0ms` 或 `1.5ms`)
|
||
- **THEN** 系统 SHALL 以错误退出并提示必须为正整数毫秒
|
||
|
||
#### Scenario: size 格式非法
|
||
- **WHEN** maxBodyBytes 或 maxOutputBytes 值不是有效的 size 格式
|
||
- **THEN** 系统 SHALL 以错误退出并提示支持 B、KB、MB、GB 格式
|
||
|
||
#### Scenario: size 解析结果非法
|
||
- **WHEN** maxBodyBytes 或 maxOutputBytes 解析结果不是非负安全整数字节数(如 `1.5B`)
|
||
- **THEN** 系统 SHALL 以错误退出并提示必须为非负安全整数字节数
|
||
|
||
#### Scenario: HTTP method 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.method` 不是 GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS 之一
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 method 不合法
|
||
|
||
#### Scenario: HTTP method 小写非法
|
||
- **WHEN** YAML 中某个 HTTP target 配置 `http.method: get`
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 method 必须为大写枚举值
|
||
|
||
#### Scenario: URL 格式非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.url` 不是合法 URL,或协议不是 `http:` / `https:`
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 URL 格式不合法
|
||
|
||
#### Scenario: maxRedirects 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.maxRedirects` 为负数
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 maxRedirects 必须为非负整数
|
||
|
||
#### Scenario: maxRedirects 非整数非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.maxRedirects` 不是非负整数(如 `1.5` 或 `"5"`)
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 maxRedirects 必须为非负整数
|
||
|
||
#### Scenario: ignoreSSL 类型非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.ignoreSSL` 不是布尔值
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 ignoreSSL 必须为布尔值
|
||
|
||
#### Scenario: HTTP headers 类型非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.headers` 不是对象,或任一 header 值不是字符串
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 http.headers 格式错误
|
||
|
||
#### Scenario: HTTP body 类型非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.body` 已配置但不是字符串
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 http.body 必须为字符串
|
||
|
||
#### Scenario: maxBodyBytes 数字非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.maxBodyBytes` 或 defaults.http.maxBodyBytes 是负数、非整数或非安全整数
|
||
- **THEN** 系统 SHALL 以错误退出,提示 maxBodyBytes 必须为非负安全整数字节数或合法 size 字符串
|
||
|
||
#### Scenario: status 模式非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `expect.status` 包含不符合 `1xx` 到 `5xx` 格式的字符串(如 `"abc"`、`"2x"`、`"6xx"`)
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 status 模式不合法
|
||
|
||
#### Scenario: status 数字非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `expect.status` 包含非整数或不在 100-599 范围内的数字
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 status 数字不合法
|
||
|
||
#### Scenario: maxDurationMs 非法
|
||
- **WHEN** YAML 中某个 target 的 `expect.maxDurationMs` 不是非负有限数字
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 expect.maxDurationMs 格式错误
|
||
|
||
#### Scenario: HTTP expect headers 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `expect.headers` 不是对象,或某个 header 期望既不是字符串也不是合法 operator
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 expect.headers 格式错误
|
||
|
||
#### Scenario: HTTP expect body 必须为数组
|
||
- **WHEN** YAML 中某个 HTTP target 的 `expect.body` 已配置但不是数组
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 expect.body 必须为数组
|
||
|
||
#### Scenario: HTTP body rule 缺少支持字段
|
||
- **WHEN** YAML 中某个 HTTP target 的 `expect.body` 数组项未包含 contains、regex、json、css、xpath 任一支持字段
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 body rule 缺少支持的规则类型
|
||
|
||
#### Scenario: HTTP body rule 同时配置多个支持字段
|
||
- **WHEN** YAML 中某个 HTTP target 的同一条 body rule 同时包含 contains、regex、json、css、xpath 中的多个支持字段
|
||
- **THEN** 系统 SHALL 以错误退出,提示每条 body rule 只能配置一种规则类型
|
||
|
||
#### Scenario: HTTP body regex 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 body regex 规则不是字符串或不是可编译正则表达式
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 body regex 不合法
|
||
|
||
#### Scenario: HTTP body json path 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 body json 规则缺少 path,或 path 不符合系统支持的 JSONPath 子集
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 body json path 不合法
|
||
|
||
#### Scenario: HTTP body css selector 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 body css 规则缺少 selector,或 selector 不是非空字符串
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 body css selector 不合法
|
||
|
||
#### Scenario: HTTP body xpath path 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 body xpath 规则缺少 path,或 path 不是非空字符串,或可被现有 XPath 库静态判定为语法错误
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 body xpath path 不合法
|
||
|
||
#### Scenario: expect operator 类型非法
|
||
- **WHEN** YAML 中某个 HTTP expect operator 的 match 不是可编译正则字符串,empty/exists 不是布尔值,或 gt/gte/lt/lte 不是有限数字
|
||
- **THEN** 系统 SHALL 以错误退出,提示对应 operator 配置不合法
|
||
|
||
#### Scenario: expect operator 类型非法
|
||
- **WHEN** YAML 中某个 expect operator 的 match 不是可编译正则字符串,empty/exists 不是布尔值,或 gt/gte/lt/lte 不是有限数字
|
||
- **THEN** 系统 SHALL 以错误退出,提示对应 operator 配置不合法
|
||
|
||
#### Scenario: unknown 字段失败
|
||
- **WHEN** YAML 中任一结构化配置对象包含契约未声明的字段,且该对象不是明确允许动态键的对象
|
||
- **THEN** 系统 SHALL 以错误退出,提示未知字段所在路径
|
||
|
||
#### Scenario: 动态 headers 字段允许
|
||
- **WHEN** YAML 中 `http.headers`、`defaults.http.headers` 或 `expect.headers` 包含任意 header 名称,且对应值符合契约
|
||
- **THEN** 系统 SHALL 接受这些动态 header 名称
|
||
|
||
#### Scenario: 动态 env 字段允许
|
||
- **WHEN** YAML 中 `cmd.env` 包含任意环境变量名称,且对应值为字符串
|
||
- **THEN** 系统 SHALL 接受这些动态 env 名称
|
||
|
||
#### Scenario: JSON Schema 不修改输入
|
||
- **WHEN** 系统执行 JSON Schema 契约校验
|
||
- **THEN** 系统 MUST NOT 通过契约校验器强制转换类型、注入默认值或删除未知字段
|
||
|
||
#### Scenario: 配置生命周期分离
|
||
- **WHEN** 系统加载配置文件
|
||
- **THEN** 系统 SHALL 按 `unknown -> RawProbeConfig -> ValidatedProbeConfig -> ResolvedConfig` 的顺序执行契约校验、语义校验和运行期配置解析
|
||
|
||
#### Scenario: 结构化校验 issue
|
||
- **WHEN** 契约校验或语义 validator 发现非法配置
|
||
- **THEN** 系统 SHALL 先生成包含 code、path、message 和可选 targetName 的结构化 `ConfigValidationIssue`,再统一渲染为中文错误
|
||
|
||
#### Scenario: 导出配置 JSON Schema
|
||
- **WHEN** 仓库生成或检查配置契约
|
||
- **THEN** 根目录 SHALL 存在 draft-07 `probe-config.schema.json`,且其内容 SHALL 与当前公共 fragments 和已注册 checker fragments 组装出的完整 schema 一致
|
||
系统 SHALL 支持使用单位字符串配置读取上限,单位包括 `B`、`KB`、`MB` 和 `GB`。
|
||
|
||
#### Scenario: 解析 MB
|
||
- **WHEN** YAML 中配置 `maxBodyBytes: "100MB"`
|
||
- **THEN** 系统 SHALL 将其解析为 104857600 bytes
|
||
|
||
#### Scenario: 解析 KB
|
||
- **WHEN** YAML 中配置 `maxOutputBytes: "512KB"`
|
||
- **THEN** 系统 SHALL 将其解析为 524288 bytes
|
||
|
||
### Requirement: runtime 并发配置
|
||
系统 SHALL 支持 `runtime.maxConcurrentChecks` 配置全局最大并发检查数。
|
||
|
||
#### Scenario: 使用默认并发限制
|
||
- **WHEN** YAML 中未配置 runtime.maxConcurrentChecks
|
||
- **THEN** 系统 SHALL 使用默认值 20
|
||
|
||
#### Scenario: 配置并发限制
|
||
- **WHEN** YAML 中配置 `runtime.maxConcurrentChecks: 5`
|
||
- **THEN** 系统 SHALL 将全局最大并发检查数设置为 5
|
||
|
||
### Requirement: YAML 配置使用 Bun 内置解析
|
||
系统 SHALL 使用 Bun 内置的 `Bun.YAML.parse()` 解析配置文件,不引入外部 YAML 解析库。
|
||
|
||
#### Scenario: 解析 YAML 内容
|
||
- **WHEN** 系统读取 YAML 文件内容
|
||
- **THEN** 系统 SHALL 调用 `Bun.YAML.parse()` 将内容解析为配置对象
|
||
|
||
### Requirement: expect 配置增强
|
||
系统 SHALL 支持 typed target 的领域专用 expect 配置,包括 HTTP 的 `status`(支持精确数字和范围模式)、`headers`、`body` 和 cmd 的 `exitCode`、`stdout`、`stderr`。内容类 expect MUST 使用数组表达配置顺序。
|
||
|
||
#### Scenario: 解析 HTTP expect 配置
|
||
- **WHEN** YAML 配置文件中 HTTP target 的 expect 包含 status、headers、body 规则数组及内部方法
|
||
- **THEN** 系统 SHALL 正确解析并存储为 HTTP target 的 expect 字段
|
||
|
||
#### Scenario: 解析 cmd expect 配置
|
||
- **WHEN** YAML 配置文件中 cmd target 的 expect 包含 exitCode、stdout 和 stderr 规则数组
|
||
- **THEN** 系统 SHALL 正确解析并存储为 cmd target 的 expect 字段
|
||
|
||
#### Scenario: 解析 body 有序规则数组
|
||
- **WHEN** YAML 中 HTTP target 配置 `expect.body` 为 contains、json、regex 三个数组项
|
||
- **THEN** 系统 SHALL 保留数组顺序,供执行阶段按配置顺序快速失败
|
||
|
||
#### Scenario: 不配置 HTTP status
|
||
- **WHEN** HTTP target 未配置 `expect.status`
|
||
- **THEN** 系统 SHALL 在执行 expect 时使用默认 `status: [200]` 语义
|
||
|
||
#### Scenario: 配置 HTTP status 范围模式
|
||
- **WHEN** HTTP target 配置 `expect.status: ["2xx"]`
|
||
- **THEN** 系统 SHALL 在执行 expect 时匹配所有 200-299 状态码
|
||
|
||
#### Scenario: 配置 HTTP status 混合模式
|
||
- **WHEN** HTTP target 配置 `expect.status: ["2xx", 301]`
|
||
- **THEN** 系统 SHALL 在执行 expect 时匹配所有 200-299 状态码或精确匹配 301
|
||
|
||
#### Scenario: 不配置 cmd exitCode
|
||
- **WHEN** cmd target 未配置 `expect.exitCode`
|
||
- **THEN** 系统 SHALL 在执行 expect 时使用默认 `exitCode: [0]` 语义
|
||
|
||
#### Scenario: 不配置 expect
|
||
- **WHEN** target 未配置任何 expect 规则
|
||
- **THEN** 系统 SHALL 正常处理,expect 字段为 undefined
|
||
|
||
### Requirement: 数据保留配置字段
|
||
配置 schema 的 `runtime` 段 SHALL 支持 `retention` 字段,类型为字符串,格式为 `<数字><单位>`(单位:`d` 天、`h` 小时、`m` 分钟),用于指定历史数据保留时长。
|
||
|
||
#### Scenario: retention 字段校验通过
|
||
- **WHEN** 配置文件中 `runtime.retention` 为合法格式(如 `"7d"`、`"24h"`、`"30m"`)
|
||
- **THEN** 配置校验 SHALL 通过
|
||
|
||
#### Scenario: retention 字段格式非法
|
||
- **WHEN** 配置文件中 `runtime.retention` 为非法格式(如 `"abc"`、`"7x"`、`""`)
|
||
- **THEN** 配置校验 SHALL 失败并报告格式错误
|
||
|
||
#### Scenario: retention 字段缺省
|
||
- **WHEN** 配置文件中未指定 `runtime.retention`
|
||
- **THEN** 系统 SHALL 使用默认值 `"7d"`
|
||
|
||
### Requirement: 数据目录路径解析
|
||
配置加载流程 SHALL 将 `server.dataDir` 相对路径基于配置文件所在目录(configDir)解析为绝对路径。绝对路径 SHALL 保持不变。
|
||
|
||
#### Scenario: dataDir 为相对路径
|
||
- **WHEN** 配置文件位于 `/opt/dial/probes.yaml`,且 `server.dataDir` 配置为 `./data`
|
||
- **THEN** 系统 SHALL 将 dataDir 解析为 `/opt/dial/data`,而非依赖进程 cwd
|
||
|
||
#### Scenario: dataDir 为绝对路径
|
||
- **WHEN** `server.dataDir` 配置为 `/var/lib/dial/data`
|
||
- **THEN** 系统 SHALL 直接使用该绝对路径,不做额外解析
|
||
|
||
#### Scenario: dataDir 使用默认值
|
||
- **WHEN** 未配置 `server.dataDir`(使用默认值 `./data`)
|
||
- **THEN** 系统 SHALL 将默认值 `./data` 基于 configDir 解析为绝对路径
|