1
0
Files
DiAL/openspec/specs/backend-code-quality/spec.md

6.5 KiB
Raw Blame History

Purpose

定义后端代码中 es-toolkit 和 Bun 内置 API 的使用规范:类型判断、空值检测、深度比较、错误判断、并发控制、集合分组和 Web API 标准方法,替代手写实现落实库使用优先级规则。

Requirements

Requirement: 使用 es-toolkit 进行类型判断

系统 SHALL 使用 es-toolkit 的 isPlainObject 替代手写的对象类型判断函数,用于 expect 校验中区分纯值(原始值)和操作符对象。

Scenario: 识别纯对象为操作符

  • WHEN body 校验规则中 expected 配置为 { equals: "value" }(纯对象操作符)
  • THEN isPlainObject(expected) SHALL 返回 true系统按操作符语义处理

Scenario: 排除非纯对象作为操作符

  • WHEN body 校验规则中 expected 为原始值如 "value" 或数字 200
  • THEN isPlainObject(expected) SHALL 返回 false系统按 equals 默认操作符处理

Requirement: 使用 es-toolkit 进行空值检测

系统 SHALL 使用 es-toolkit 的 isNil 替代手写的 actual === null || actual === undefined 检测,用于 expect 中 empty 操作符的空值判断。

Scenario: null 值判定为空

  • WHEN 校验值为 null
  • THEN isNil(null) SHALL 返回 true

Scenario: undefined 值判定为空

  • WHEN 校验值为 undefined
  • THEN isNil(undefined) SHALL 返回 true

Scenario: 非空值判定为非空

  • WHEN 校验值为 0、"false"、空数组 [] 等非 nil 值
  • THEN isNil(value) SHALL 返回 false

Requirement: 使用 es-toolkit 进行空对象检测

系统 SHALL 使用 es-toolkit 的 isEmptyObject 替代手写的 typeof actual === "object" && Object.keys(actual).length === 0 检测,用于 expect 中 empty 操作符的空对象判断。

Scenario: 空对象判定为空

  • WHEN 校验值为 {}
  • THEN isEmptyObject({}) SHALL 返回 true

Scenario: 非空对象判定为非空

  • WHEN 校验值为 { key: "val" }
  • THEN isEmptyObject({ key: "val" }) SHALL 返回 false

Scenario: null 不是空对象

  • WHEN 校验值为 null
  • THEN isEmptyObject(null) SHALL 返回 false空值由 isNil 前置处理)

Requirement: 使用 es-toolkit 进行深度相等比较

系统 SHALL 使用 es-toolkit 的 isEqual 替代 !== 浅比较,用于 expect 中 equals 操作符的值比较,支持对象和数组的深度比较。

Scenario: 原始值浅比较

  • WHEN expected 和 actual 均为原始值字符串、数字、布尔值、null
  • THEN isEqual(actual, expected) 的行为 SHALL 与 actual === expected 一致

Scenario: 对象深度比较

  • WHEN expected 和 actual 均为对象(如从 JSONPath 提取的结构化数据)
  • THEN isEqual(actual, expected) SHALL 递归比较所有属性值,而非引用比较

Requirement: 使用 es-toolkit 进行错误类型判断

系统 SHALL 使用 es-toolkit 的 isError 替代 error instanceof Error,用于 HTTP runner 和 cmd runner 中的错误类型判断。

Scenario: Error 实例识别

  • WHEN 错误对象为 new Error("msg")
  • THEN isError(error) SHALL 返回 true

Scenario: Error 子类识别

  • WHEN 错误对象为继承 Error 的自定义类型
  • THEN isError(error) SHALL 返回 true

Scenario: 非 Error 对象识别

  • WHEN 错误对象为字符串或普通对象
  • THEN isError(error) SHALL 返回 false

Requirement: 使用 es-toolkit Semaphore 实现并发控制

系统 SHALL 使用 es-toolkit 的 Semaphore 类替代手写的信号量实现(计数器 + Promise 队列),用于 ProbeEngine 中的组内并发拨测控制。

Scenario: 获取并发槽位

  • WHEN 当前并发数未达上限
  • THEN semaphore.acquire() SHALL 立即返回,不阻塞

Scenario: 等待并发槽位

  • WHEN 当前并发数已达上限 maxConcurrentChecks
  • THEN semaphore.acquire() SHALL 阻塞等待,直到其他任务调用 semaphore.release()

Scenario: 释放并发槽位

  • WHEN 调用 semaphore.release()
  • THEN 系统 SHALL 唤醒一个等待中的 acquire() 调用

Requirement: 使用 es-toolkit groupBy 实现 target 分组

系统 SHALL 使用 es-toolkit 的 groupBy 函数替代手写的 Map 循环分组,用于 ProbeEngine 中按 interval 分组拨测目标。

Scenario: 按 interval 分组

  • WHEN 输入包含不同 intervalMs 值的多个 target
  • THEN groupBy(targets, t => t.intervalMs) SHALL 返回 key 为 intervalMs 值的分组对象,值为对应 target 数组

Requirement: 使用原生 API 进行数组类型判断

系统 SHALL 使用原生 Array.isArray() 替代 es-toolkit/compatisArray,用于 checker 模块中所有数组类型判断场景。

Scenario: 数组值判定为数组

  • WHEN 校验值为数组(如 [1, 2, 3]
  • THEN Array.isArray(value) SHALL 返回 true

Scenario: 非数组值判定为非数组

  • WHEN 校验值为对象 {}、字符串 "abc"、数字 123、null 等
  • THEN Array.isArray(value) SHALL 返回 false

Requirement: 使用原生 API 进行对象类型判断

系统 SHALL 使用原生 typeof x === 'object' && x !== null 替代 es-toolkit/compatisObject,用于 checker 模块中需要判断值为对象类型(排除 null的场景。

Scenario: 普通对象判定为对象

  • WHEN 值为普通对象 { key: "val" }
  • THEN typeof value === 'object' && value !== null SHALL 返回 true

Scenario: 数组判定为对象

  • WHEN 值为数组 [1, 2, 3]
  • THEN typeof value === 'object' && value !== null SHALL 返回 true

Scenario: Headers 实例判定为对象

  • WHEN 值为 Headers 实例
  • THEN typeof value === 'object' && value !== null SHALL 返回 true

Scenario: null 判定为非对象

  • WHEN 值为 null
  • THEN typeof value === 'object' && value !== null SHALL 返回 false

Scenario: 原始值判定为非对象

  • WHEN 值为字符串、数字、布尔值、undefined
  • THEN typeof value === 'object' && value !== null SHALL 返回 false

Requirement: 使用 Bun 内置 API 进行 Headers 转换

系统 SHALL 使用 Object.fromEntries(headers) 标准 Web API 替代手写的 headersToRecord 函数,用于将 Fetch API 的 Headers 对象转换为键值对。

Scenario: 转换响应头

  • WHEN HTTP runner 获取到 response headers
  • THEN Object.fromEntries(response.headers) SHALL 返回以 header 名称为 key、header 值为 value 的对象