枚举生成
标准 OpenAPI enum 默认仍由 openapi-typescript 生成 literal union。页面需要运行时常量、Options、label、颜色或图标时,在对应 service 显式启用 createRuntimeEnumHooks;后端非标准格式先由业务 preprocessSwagger 规范化。
接入多个后端时先确认实际 JSON 值,再按多后端接入流程编写 service 级 normalizer。不要根据框架名称全局猜测 enum 格式。
标准输入
{
"type": "integer",
"enum": [0, 1],
"x-enum-varnames": ["Offline", "Online"],
"x-enum-labels": ["离线", "在线"]
}enum 是真实请求/响应值,x-enum-varnames 是代码成员名,x-enum-labels 是默认页面文案。三者长度必须一致;没有 labels 时回退到 varname。
页面 metadata 配置
import { createRuntimeEnumHooks } from "@xapiforge/genapi";
const enumHooks = createRuntimeEnumHooks({
metadata: {
"Sample.DeviceState": {
// 按真实 enum value 覆盖业务文案和代码成员名。
overrideLabels: { 0: "未连接", 1: "已连接" },
overrideVarnames: { 1: "Connected" },
// 后端 Swagger 确实缺值时显式补充;冲突直接失败。
appendValues: [{ name: "Unknown", label: "未知状态", value: 2 }],
// 通用页面属性,不写死 color/icon,也可以配置 tagType、badge、unit 等。
extensions: {
colors: { 0: "#999", 1: "#16a34a", 2: "#f59e0b" },
icon: { 0: "WifiOff", 1: "Wifi" },
presentation: {
0: { tone: "muted", order: 2 },
1: { tone: "positive", order: 1 }
}
},
// 数字 enum 实际按字符串传输时生成转换映射;普通接口不要配置。
serializeAs: "string"
}
}
});metadata 支持:
overrideLabels:修改页面 label;overrideVarnames:修改 TypeScript 成员名;appendValues:补充 Swagger 缺失的值,并同步进入 schema union;extensions:按 value 配置任意 JSON 页面属性;serializeAs:为数字枚举生成字符串序列化/反序列化映射;isFlags:Flags schema 的接口类型保持number。
配置只应放在确实包含目标 schema 的 service hook 上。这样拼错的 full schema name 会立即失败,不会在多服务项目里静默失效。
生成产物
原始 service 类型文件生成:
export const SampleDeviceState = {
Offline: 0,
Connected: 1,
Unknown: 2
} as const;
export type SampleDeviceStateValue = (typeof SampleDeviceState)[keyof typeof SampleDeviceState];
export const SampleDeviceStateLabels = {
[SampleDeviceState.Offline]: "未连接",
[SampleDeviceState.Connected]: "已连接",
[SampleDeviceState.Unknown]: "未知状态"
} as const;
export const SampleDeviceStateOptions = [
{ key: "Offline", value: SampleDeviceState.Offline, label: "未连接" },
{ key: "Connected", value: SampleDeviceState.Connected, label: "已连接" },
{ key: "Unknown", value: SampleDeviceState.Unknown, label: "未知状态" }
] as const;
export type SampleDeviceStateOption = (typeof SampleDeviceStateOptions)[number];
export const SampleDeviceStateColors = {
[SampleDeviceState.Offline]: "#999",
[SampleDeviceState.Connected]: "#16a34a",
[SampleDeviceState.Unknown]: "#f59e0b"
} as const;
export const SampleDeviceStateIcon = {
[SampleDeviceState.Offline]: "WifiOff",
[SampleDeviceState.Connected]: "Wifi",
[SampleDeviceState.Unknown]: null
} as const;还会生成 Names、ByLabel、getXxxLabel()、getXxxName();serializeAs: "string" 时生成 Serialize、Deserialize 和转换函数。extension 缺失值固定补 null,对象 key 会稳定排序,值必须是有限、无循环的 JSON。
跟随 namespace
runtime enum 与 schema 使用同一套 service、prefix、显式 alias 和 flat-strict 规则。类型和值可以同名:
type State = BaseSchemas.DeviceState;
const state: State = BaseSchemas.DeviceState.Connected;
const options = BaseSchemas.DeviceStateOptions;
const color = BaseSchemas.DeviceStateColors[state];
const icon = BaseSchemas.DeviceStateIcon[state];命中前缀 namespace 后整组产物一起移动:
const options = FillCenterSchemas.ReportStatistics.StatusOptions;flat-strict 在 project 顶层或其 prefix namespace 提供同样产物。canonical-only 不生成便利短名,使用稳定注册表:
const state = RuntimeEnumsByService.base["Sample.DeviceState"].value.Connected;
const options = RuntimeEnumsByService.base["Sample.DeviceState"].options;新增 enum 的 Options/Labels 不会移动其它 schema 或其它 enum 的导出;若新名称与现有 schema、namespace 或扩展产物碰撞,生成会在事务写盘前失败。
不重新读取 Swagger
original Swagger
→ preprocessSwagger(业务 legacy 规范化)
→ metadata override/append 合并一次
→ normalizedDocument(深度冻结)
├─ openapi-typescript
├─ schema/namespace inventory
└─ transformTypes 生成 runtime enumtransformTypes 只读取 ctx.normalizedDocument 和创建 hook 时的 metadata 快照,不读取 ctx.swaggerPath,也不重新执行 preprocess。这样 label、schema union、Options 和扩展映射来自同一份权威数据,并发生成多个 service 时不会串数据。
Flags
createRuntimeEnumHooks({
metadata: {
"Sample.AccessMode": { isFlags: true }
}
});Flags 的 OpenAPI 字段生成 number,runtime const/Options 仍只列出 Swagger 声明的基础成员,并额外生成 XxxMember。组合值由业务代码按位计算:
const combined: number = AccessMode.Read | AccessMode.Execute;legacy enum
下面不是合法的标准数字 enum:
{ "type": "integer", "enum": ["Offline(离线)=0", "Online(在线)=1"] }业务 normalizer 应整体、保守地转换为数字 enum + varnames + labels,再交给通用 helper:
export const legacyEnumHooks = createRuntimeEnumHooks({
preprocessSwagger: swagger => Promise.resolve(normalizeLegacyIntegerEnums(swagger)),
metadata: {
"Fixture.LegacyPermissions": { isFlags: true }
}
});可运行示例见 examples/demo-app/hooks/legacy-enum.ts。框架特有解析仍属于业务 hook,通用 helper 只处理规范化后的标准 enum 和明确 metadata。
失败边界
生成前严格检查:
- metadata full schema name、override/extension value 必须存在;
- append value 类型必须与 schema 一致,且不能与已有值冲突;
- varname 必须是非保留 TypeScript 标识符,name/value 不能重复;
- extension 名转换后的导出名不能互相碰撞,也不能覆盖 Labels/Options 等内置产物;
- extension 只能包含确定性 JSON;
- runtime enum 导出不能与同 namespace 的 schema 或 prefix namespace 冲突;
serializeAs: "string"只允许数字枚举。
任一失败都会中止整个 project,正式输出目录保持上一次有效结果。