## Purpose 定义配置文件的变量定义、引用、解析和替换机制,支持集中管理共享值和环境变量注入。 ## Requirements ### Requirement: variables 段定义 配置文件 SHALL 支持可选的顶层 `variables` 段,用于定义变量键值对。variables 的 key SHALL 符合 `[a-zA-Z_][a-zA-Z0-9_]*` 命名规则。variables 的 value SHALL 仅支持 string、number、boolean 三种类型,MUST NOT 支持 null、array、object。variables 段自身 MUST NOT 支持引用其他变量或环境变量(值为纯字面量)。 #### Scenario: 定义字符串变量 - **WHEN** 配置文件包含 `variables: { base_url: "https://api.example.com" }` - **THEN** 系统 SHALL 解析 base_url 为字符串类型变量,值为 "https://api.example.com" #### Scenario: 定义数字变量 - **WHEN** 配置文件包含 `variables: { port: 5432 }` - **THEN** 系统 SHALL 解析 port 为 number 类型变量,值为 5432 #### Scenario: 定义布尔变量 - **WHEN** 配置文件包含 `variables: { ssl_enabled: true }` - **THEN** 系统 SHALL 解析 ssl_enabled 为 boolean 类型变量,值为 true #### Scenario: 变量值为 null 报错 - **WHEN** 配置文件包含 `variables: { empty: null }` - **THEN** 系统 SHALL 以配置错误退出,提示 variables 的值不允许为 null #### Scenario: 变量值为数组报错 - **WHEN** 配置文件包含 `variables: { list: [1, 2, 3] }` - **THEN** 系统 SHALL 以配置错误退出,提示 variables 的值不允许为 array #### Scenario: 变量值为对象报错 - **WHEN** 配置文件包含 `variables: { obj: { a: 1 } }` - **THEN** 系统 SHALL 以配置错误退出,提示 variables 的值不允许为 object #### Scenario: 变量 key 不合法报错 - **WHEN** 配置文件包含 `variables: { "123start": "value" }` - **THEN** 系统 SHALL 以配置错误退出,提示变量名不符合命名规则 #### Scenario: 不定义 variables 段 - **WHEN** 配置文件不包含 variables 段 - **THEN** 系统 SHALL 正常启动,targets 中的 `${...}` 引用仅从环境变量查找 ### Requirement: 变量引用语法 targets 中的字符串值 SHALL 支持 `${key}` 语法引用变量。系统 SHALL 支持 `${key|default}` 语法设置默认值,其中第一个 `|` 为分隔符,后续 `|` 属于默认值内容。系统 SHALL 支持 `$${...}` 转义语法输出字面量 `${...}`。 #### Scenario: 简单变量引用 - **WHEN** target 字段值为 `"${base_url}/health"` 且 variables 中定义 `base_url: "https://api.example.com"` - **THEN** 系统 SHALL 将该字段替换为 `"https://api.example.com/health"` #### Scenario: 带默认值的变量引用 - **WHEN** target 字段值为 `"${DB_PORT|5432}"` 且 variables 和环境变量中均不存在 DB_PORT - **THEN** 系统 SHALL 将该字段替换为使用默认值(类型推断后为 number 5432) #### Scenario: 默认值包含管道符 - **WHEN** target 字段值为 `"${PATTERN|foo|bar}"` 且变量不存在 - **THEN** 系统 SHALL 使用 `"foo|bar"` 作为默认值(第一个 `|` 为分隔符) #### Scenario: 转义语法 - **WHEN** target 字段值为 `"Hello $${name}"` - **THEN** 系统 SHALL 输出 `"Hello ${name}"`,不进行变量替换 #### Scenario: 多个变量引用 - **WHEN** target 字段值为 `"${protocol}://${host}:${port}/api"` - **THEN** 系统 SHALL 逐个解析并替换所有变量引用,结果为拼接后的字符串 #### Scenario: 无变量引用的字符串 - **WHEN** target 字段值为 `"https://example.com"` 且不含 `${...}` 模式 - **THEN** 系统 SHALL 保持原样,不做任何处理 ### Requirement: 变量解析优先级 系统 SHALL 按以下优先级解析变量引用:variables 定义 → 环境变量 → 默认值。如果三者均不存在,系统 SHALL 以配置错误退出。 #### Scenario: variables 优先于环境变量 - **WHEN** variables 中定义 `port: 5432` 且环境变量 `port=3000` 也存在 - **THEN** 系统 SHALL 使用 variables 中的值 5432 #### Scenario: 环境变量作为 fallback - **WHEN** variables 中未定义 `DB_HOST` 但环境变量 `DB_HOST=localhost` 存在 - **THEN** 系统 SHALL 使用环境变量的值 "localhost" #### Scenario: 默认值作为最终 fallback - **WHEN** variables 和环境变量中均不存在 `CACHE_TTL`,且引用为 `${CACHE_TTL|60}` - **THEN** 系统 SHALL 使用默认值(类型推断后为 number 60) #### Scenario: 变量未定义且无默认值报错 - **WHEN** target 字段引用 `${MISSING_VAR}` 且 variables、环境变量中均不存在,也未设置默认值 - **THEN** 系统 SHALL 以配置错误退出,提示该变量未定义 ### Requirement: 完整引用类型保留 当字段值仅包含单个变量引用(完整引用)时,系统 SHALL 保留变量的原始类型。完整引用的判定为:字段值去掉首尾空白后严格匹配单个 `${key}` 或 `${key|default}` 模式且无其他字符。 #### Scenario: 完整引用 number 变量 - **WHEN** target 字段值为 `"${port}"` 且 variables 中 `port: 5432` - **THEN** 系统 SHALL 将该字段替换为 number 类型的 5432 #### Scenario: 完整引用 boolean 变量 - **WHEN** target 字段值为 `"${ssl}"` 且 variables 中 `ssl: true` - **THEN** 系统 SHALL 将该字段替换为 boolean 类型的 true #### Scenario: 完整引用 string 变量 - **WHEN** target 字段值为 `"${host}"` 且 variables 中 `host: "example.com"` - **THEN** 系统 SHALL 将该字段替换为 string 类型的 "example.com" #### Scenario: 部分引用强制为字符串 - **WHEN** target 字段值为 `"port: ${port}"` 且 variables 中 `port: 5432` - **THEN** 系统 SHALL 将该字段替换为 string 类型的 "port: 5432" #### Scenario: 环境变量完整引用类型推断 - **WHEN** target 字段值为 `"${MAX_REDIRECTS}"` 且环境变量 `MAX_REDIRECTS=5` - **THEN** 系统 SHALL 将该字段替换为 number 类型的 5(类型推断) #### Scenario: 默认值完整引用类型推断 - **WHEN** target 字段值为 `"${TIMEOUT|30}"` 且变量不存在 - **THEN** 系统 SHALL 将该字段替换为 number 类型的 30(类型推断) #### Scenario: 默认值推断为 boolean - **WHEN** target 字段值为 `"${IGNORE_SSL|false}"` 且变量不存在 - **THEN** 系统 SHALL 将该字段替换为 boolean 类型的 false #### Scenario: 默认值无法推断保持字符串 - **WHEN** target 字段值为 `"${HOST|localhost}"` 且变量不存在 - **THEN** 系统 SHALL 将该字段替换为 string 类型的 "localhost" ### Requirement: 替换范围限制 变量替换 SHALL 仅作用于 targets 段。`id` 和 `type` 字段 MUST NOT 参与变量替换。`server`、`runtime`、`defaults` 段 MUST NOT 参与变量替换。系统 SHALL 递归遍历 target 对象树中所有字符串值进行替换(包括嵌套对象和数组元素中的字符串)。 #### Scenario: target 嵌套对象中的变量替换 - **WHEN** target 配置 `http.headers.Authorization: "${token}"` 且 variables 中定义 `token: "Bearer abc"` - **THEN** 系统 SHALL 将该 header 值替换为 "Bearer abc" #### Scenario: target 数组元素中的变量替换 - **WHEN** target 配置 `cmd.args: ["--host", "${host}"]` 且 variables 中定义 `host: "localhost"` - **THEN** 系统 SHALL 将数组第二个元素替换为 "localhost" #### Scenario: id 字段不替换 - **WHEN** target 配置 `id: "${my_id}"` 且 variables 中定义 `my_id: "test"` - **THEN** 系统 SHALL 保持 id 字段值为字面量 `"${my_id}"`,不进行替换 #### Scenario: type 字段不替换 - **WHEN** target 配置 `type: "${checker_type}"` 且 variables 中定义 `checker_type: "http"` - **THEN** 系统 SHALL 保持 type 字段值为字面量 `"${checker_type}"`,不进行替换 #### Scenario: defaults 段不替换 - **WHEN** defaults 配置 `interval: "${default_interval}"` 且 variables 中定义 `default_interval: "30s"` - **THEN** 系统 SHALL 保持 defaults.interval 为字面量 `"${default_interval}"`,不进行替换 #### Scenario: server 段不替换 - **WHEN** server 配置 `host: "${server_host}"` 且 variables 中定义 `server_host: "0.0.0.0"` - **THEN** 系统 SHALL 保持 server.host 为字面量 `"${server_host}"`,不进行替换 ### Requirement: 变量替换错误报告 变量替换阶段的错误 SHALL 作为 `ConfigValidationIssue` 输出,code 为 `unresolved-variable`。错误信息 SHALL 包含 target 索引、target id、字段路径和变量名。 #### Scenario: 单个变量缺失报错 - **WHEN** targets[0] (id: "api-health") 的 http.url 引用 `${base_url}` 但变量不存在 - **THEN** 系统 SHALL 输出包含 target 索引 0、id "api-health"、路径 "http.url"、变量名 "base_url" 的错误信息 #### Scenario: 多个变量缺失批量报错 - **WHEN** 多个 target 的多个字段引用了不存在的变量 - **THEN** 系统 SHALL 收集所有缺失变量错误后统一输出,而非遇到第一个就退出 ### Requirement: 变量替换执行时机 变量替换 SHALL 在 YAML 解析之后、schema 契约校验(AJV)之前执行。替换完成后的配置对象 SHALL 传入后续校验流程。 #### Scenario: 替换后通过 schema 校验 - **WHEN** target 配置 `http.maxRedirects: "${MAX_REDIRECTS}"` 且环境变量 `MAX_REDIRECTS=5` - **THEN** 系统 SHALL 先将该字段替换为 number 5,再进入 AJV 校验(期望 integer),校验通过 #### Scenario: 替换后未通过 schema 校验 - **WHEN** target 配置 `http.maxRedirects: "${MAX_REDIRECTS}"` 且环境变量 `MAX_REDIRECTS=abc` - **THEN** 系统 SHALL 先将该字段替换为 string "abc",再进入 AJV 校验(期望 integer),校验失败并报错