TypeSpec @typespec/openapi3 Emitter 完整使用指南:配置选项详解与源码级原理剖析
【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec
导读
@typespec/openapi3是 TypeSpec 官方提供的 OpenAPI 3 规范生成器,能够把 TypeSpec 编写的 REST API 描述编译输出为 OpenAPI 3.0 / 3.1 / 3.2 规范的 YAML 或 JSON 文档。本文将围绕该 Emitter 的启用方式与全部配置选项展开:先介绍命令行与配置文件两种使用入口,再逐个拆解output-file、file-type、openapi-versions、operation-id-strategy、enum-strategy等关键选项的类型、默认值与实战影响,并结合仓库源码(选项 Schema 校验、operationId 生成器、schema 密封逻辑等)说明每个选项的底层实现原理。读完后你将能独立完成 OpenAPI 输出的命名、版本、格式与风格定制。
一、如何启用 OpenAPI3 Emitter
1. 安装与命令行方式
@typespec/openapi3是独立 npm 包,安装方式如下(见 packages/openapi3/README.md):
npm install @typespec/openapi3安装完成后,最简单的方式是通过命令行指定 emit 目标:
tsp compile . --emit=@typespec/openapi3该命令会编译当前目录下的 TypeSpec 源文件(main.tsp),并把生成的 OpenAPI 文档输出到默认输出目录。
2. 通过 tspconfig.yaml 配置
更推荐的做法是在项目根目录的tspconfig.yaml中声明 emitter,让编译行为可复现、可纳入 CI:
emit: - "@typespec/openapi3"配置可以继续扩展为带选项的形态:
emit: - "@typespec/openapi3" options: "@typespec/openapi3": option: value其中options段下的每个键对应一个 Emitter 选项,本文第二部分将逐一说明。
从源码结构看,Emitter 的入口位于 packages/openapi3/src/openapi.ts,其导出的
$onEmit钩子会在编译阶段被 TypeSpec 编译器调用;resolveOptions负责把用户配置与默认值合并,随后遍历openapiVersions依次生成各版本的文档(见 packages/openapi3/src/index.ts 的导出清单)。
二、Emitter 选项完整详解
所有选项的类型定义与 JSON Schema 校验规则集中在 packages/openapi3/src/lib.ts 的OpenAPI3EmitterOptions接口与EmitterOptionsSchema中,下面逐个展开。
1.emitter-output-dir:输出目录
- 类型:
absolutePath - 默认值:
{output-dir}/@typespec/openapi3
该选项定义 Emitter 输出目录的绝对路径。默认情况下,输出会被放到编译器全局输出目录(output-dir)下的@typespec/openapi3子目录中。你可以在tspconfig.yaml的顶层output-dir或本选项里显式覆盖。
2.file-type:输出文件格式
- 类型:
"yaml" | "json" | ("yaml" | "json")[] - 默认值:
"yaml"(未指定时根据output-file扩展名推断)
决定文档序列化为 YAML 还是 JSON,既可以是单个值,也可以是数组以同时产出两种格式:
options: "@typespec/openapi3": file-type: - yaml - json当使用数组时,output-file中可以使用{file-type}变量生成相互区分的文件名(详见下文)。在 lib.ts 的 Schema 定义中,数组形态被约束为uniqueItems: true, minItems: 1,即不允许重复或空数组。
3.output-file:输出文件名
- 类型:
string
指定输出文件的名称,支持以下插值变量:
| 变量 | 含义 |
|---|---|
service-name | 服务名称(单服务场景) |
service-name-if-multiple | 服务名称(多服务时用于区分) |
version | 服务版本(多版本时) |
file-type | 当前输出的文件类型(json 或 yaml),在file-type为数组时特别有用 |
默认值:{service-name-if-multiple}.{version}.openapi.yaml(若file-type为"json"则扩展名为.json);当file-type为数组时默认模板为{service-name-if-multiple}.{version}.openapi.{file-type}。
文档给出了不同场景下的命名结果示例:
- 单服务、无版本:
openapi.yaml - 多服务、无版本:
openapi.Org1.Service1.yaml、openapi.Org1.Service2.yaml - 单服务、带版本:
openapi.v1.yaml、openapi.v2.yaml - 多服务、带版本:
openapi.Org1.Service1.v1.yaml、openapi.Org1.Service1.v2.yaml、openapi.Org1.Service2.v1.0.yaml、openapi.Org1.Service2.v1.1.yaml
从 openapi.ts 的实现看,文件名模板通过编译器提供的interpolatePath函数完成插值,因此服务名、版本号中的非法路径字符会被自动清理,保证生成路径合法。
4.openapi-versions:目标规范版本
- 类型:
"3.0.0" | "3.1.0" | "3.2.0" - 默认值:
["3.0.0"]
指定要产出的 OpenAPI 规范版本,支持数组一次性生成多个版本:
options: "@typespec/openapi3": openapi-versions: - "3.0.0" - "3.1.0"Schema 约束为uniqueItems: true, minItems: 1。当指定多个版本时,输出文件会被放到以各自规范版本命名的子目录中(对应 openapi.ts 中resolvePath(context.emitterOutputDir, specDir, outputFile)的逻辑)。注意:不同版本对应的 schema 生成器是分开实现的,仓库中分别有 schema-emitter-3-0.ts、schema-emitter-3-1.ts 与 schema-emitter-3-2.ts。
5.new-line:换行符
- 类型:
"crlf" | "lf" - 默认值:
"lf"
设置生成文件的换行符。跨平台 CI 或需要严格 diff 校验的团队可用crlf固定为 Windows 风格;默认lf符合 Linux/macOS 惯例。
6.omit-unreachable-types:省略不可达类型
- 类型:
boolean - 默认值:
false
默认情况下,服务命名空间(service namespace)下声明的所有类型都会被包含进输出。开启该选项后,只有被某个操作(operation)引用的类型才会被产出,未被任何接口引用的模型、枚举等会被裁剪掉。适合用于希望 OpenAPI 文档尽量精简、只描述实际 API 暴露面的场景。
7.include-x-typespec-name:调试扩展
- 类型:
"inline-only" | "never" - 默认值:
"never"
是否在生成的 schema 上附加x-typespec-name扩展字段,记录生成该 schema 的 TypeSpec 类型名称。官方明确提示:该扩展仅用于调试定位,不应被任何工具链依赖。inline-only表示只对内联类型附加,never表示完全不附加。
8.safeint-strategy:safeint 整数映射
- 类型:
"double-int" | "int64" - 默认值:
"int64"
控制 TypeSpec 的safeint类型映射为哪种 JSON Schema 数字格式:
int64:输出type: integer, format: int64double-int:输出type: integer, format: double-int
如果下游消费者(如 Java、.NET 代码生成器)对大整数支持有限,可切换为double-int以表达“双精度可安全表示”的语义。
9.seal-object-schemas:密封对象 schema
- 类型:
boolean - 默认值:
false
开启后,对于作为对象 schema 输出的模型,若模型没有显式定义additionalProperties之类的开放属性,则:
- OpenAPI 3.0 下默认
additionalProperties: false - OpenAPI 3.1 下默认
unevaluatedProperties: false
从源码看,这一逻辑在 schema-emitter.ts 的shouldSealSchema方法中实现:仅当模型没有 indexer 且没有可被包含的派生模型(derivedModels)时才会被密封,避免破坏继承模型的多态开放性。
10.experimental-parameter-examples:参数示例策略(实验性)
- 类型:
"data" | "serialized"
决定参数(parameter)上的示例如何输出。该功能标记为实验性,未来版本可能变化:
data:按参数实际数据形态输出示例serialized:按参数序列化规则(如 query/path 风格下的字符串序列化形式)输出示例
由于参数示例的序列化规则在不同 OpenAPI 风格(style/explode)下差异较大,官方同时参考了 OpenAPI 3.0.4 规范的 style-examples 规则以及社区关于参数示例处理的讨论,使用时建议先在目标规范版本上验证输出是否符合预期。
11.operation-id-strategy:operationId 生成策略
- 类型:
"parent-container" | "fqn" | "explicit-only" | object { kind, separator } - 默认值:
"parent-container"
决定当操作未使用@operationId装饰器时,如何生成operationId。三种内置策略:
| 策略 | 行为 |
|---|---|
parent-container | 用父命名空间/接口名 + 操作名生成 ID(例如Widget.read) |
fqn | 使用操作从服务根起的完整限定名生成 ID |
explicit-only | 只使用显式定义的 operationId,未定义则省略 |
此外还支持对象形态,额外指定连接各段的separator:
options: "@typespec/openapi3": operation-id-strategy: kind: parent-container separator: "_"从 operation-id-resolver.ts 的实现细节看:
- 解析顺序是:先查
@operationId(通过@typespec/openapi的getOperationId),有则直接使用;没有则按策略计算; #getOperationPath会从操作名向上回溯 interface 或 namespace,遇到全局命名空间或服务根(isService)即停止,从而得到路径段数组;parent-container取路径最后两段、fqn取全部段,用separator(默认".")拼接;- 自动去重:若两个操作算出相同 ID,后续者会被追加
_2、_3等后缀,保证 ID 唯一性(#findNextAvailableName)。
这一机制保证了即使多个命名空间下有同名操作,生成的 OpenAPI 文档也不会出现重复的operationId。
12.enum-strategy:枚举输出风格
- 类型:
"default" | "annotated" - 默认值:
"default"
控制 TypeSpec 枚举及字面量联合如何映射为 OpenAPI schema:
default:输出为单个 schema,使用enum关键字(经典写法,兼容性最好);annotated:输出为oneOf结构,每个成员/变体对应一个带title与description(来自@summary与@doc)的const子 schema,遵循 OpenAPI 3.1.1 的 annotated enumerations 模式,便于携带每个枚举值的文档说明。
需要注意:annotated仅在 OpenAPI 3.1.0 及以上支持;当目标版本为 3.0.0 时,Emitter 会回退到default风格并报告警告(对应 lib.ts 中定义的enum-strategy-not-supported诊断,消息为 "enum-strategy: annotatedis only supported for OpenAPI 3.1.0 and above...")。
三、默认值速查表
综合 openapi.ts 中defaultOptions与各选项声明,可将全部默认值汇总如下:
| 选项 | 默认值 |
|---|---|
emitter-output-dir | {output-dir}/@typespec/openapi3 |
file-type | yaml(可随output-file扩展名推断) |
output-file | {service-name-if-multiple}.{version}.openapi.yaml(json 时.json) |
openapi-versions | ["3.0.0"] |
new-line | lf |
omit-unreachable-types | false |
include-x-typespec-name | never |
safeint-strategy | int64 |
seal-object-schemas | false |
operation-id-strategy | parent-container(separator 为.) |
enum-strategy | default |
四、完整配置示例
下面是一个覆盖多个选项的tspconfig.yaml示例,可直接复制修改后使用:
emit: - "@typespec/openapi3" options: "@typespec/openapi3": emitter-output-dir: "{project-root}/generated/openapi" file-type: - yaml - json output-file: "{service-name-if-multiple}.{version}.openapi.{file-type}" openapi-versions: - "3.0.0" - "3.1.0" new-line: lf omit-unreachable-types: true safeint-strategy: int64 seal-object-schemas: true operation-id-strategy: kind: fqn separator: "_" enum-strategy: annotated上述配置将同时产出 YAML 与 JSON 两种格式、3.0.0 与 3.1.0 两个规范版本的 OpenAPI 文档,并启用类型裁剪、schema 密封、FQN 风格 operationId 与注解式枚举输出。
五、注意事项与限制
enum-strategy: annotated的版本限制:目标为 OpenAPI 3.0.0 时会自动回退到default并产生警告,若需注解式枚举,请同时配置openapi-versions包含 3.1.0。experimental-parameter-examples为实验特性:行为可能在后续版本调整,正式环境使用前请确认输出符合预期。x-typespec-name仅用于调试:官方明确不保证该扩展被下游工具兼容,不应作为稳定契约。- 多版本输出目录隔离:当
openapi-versions指定多个版本时,输出会被分别放入以版本号命名的子目录,配合output-file插值可避免文件互相覆盖。 - 选项校验严格:
EmitterOptionsSchema设置了additionalProperties: false,未在OpenAPI3EmitterOptions中定义的选项名会被直接判为非法配置,拼写错误会立即暴露。
六、延伸阅读
- Emitter 选项声明与 JSON Schema: packages/openapi3/src/lib.ts
- Emitter 主流程
$onEmit与resolveOptions: packages/openapi3/src/openapi.ts - operationId 生成与去重实现: packages/openapi3/src/operation-id-resolver/operation-id-resolver.ts
- schema 密封逻辑: packages/openapi3/src/schema-emitter.ts
- 各规范版本 schema 生成器: schema-emitter-3-0.ts、schema-emitter-3-1.ts、schema-emitter-3-2.ts
- 关联参考文档(本文主体来源): website/src/content/docs/docs/emitters/openapi3/reference/emitter.md
【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考