sgcOpenAPI 2026.9.0 是本产品迄今为止最大的一次发布。上一个版本能够处理大多数教程展示的那种规范形态,遇到其他情况就悄悄降级。这一版对照 OpenAPI 3.0、3.1 和 3.2 规范,以及真实发布的文档,逐项特性重新梳理了解析器,成果是 9 项新功能、26 个修复的缺陷和 5 项有意为之的破坏性变更。
简单来说:对于那些以前会生成无法编译的代码,或者更糟糕的是生成了能编译却调用了错误 URL 的代码的规范,现在生成的客户端是正确的。
解析器现在会告诉你它做不到什么
旧的解析器只有一种报告问题的方式,就是抛出异常,而对其余一切只有一种处理方式,就是默默继续。它无法生成的操作干脆就不出现在输出里,而你只有在去找一个并不存在的方法时才会发现。
现在每份文档解析后都会带回一个 Warnings 列表。缺失的 openapi 或 info 成员、JSON 类型错误的成员、无法生成的操作、无法解析的路径项引用,以及已读取但尚未生效的 JSON Schema 关键字,都会记录在那里。每次读取都会清空该列表,因此你拿到的内容始终属于刚刚解析的那份文档。
uses
sgcOpenAPI_Classes, sgcOpenAPI_Parser_Client_Pascal;
var
oParser: TsgcOpenAPI_Parser_Client_Pascal;
i: Integer;
begin
oParser := TsgcOpenAPI_Parser_Client_Pascal.Create;
Try
oParser.OpenAPIClassName := 'TPetStoreClient';
oParser.OpenAPINamespace := 'PetStore';
oParser.OutputFileName := 'PetStoreClient.pas';
oParser.ReadFromFile('petstore.json');
for i := 0 to oParser.Warnings.Count - 1 do
Memo1.Lines.Add('warning: ' + oParser.Warnings[i]);
oParser.SaveToFile('PetStoreClient.pas');
Finally
oParser.Free;
End;
end;
保存之前请先设置 OutputFileName。只有当 Pascal 单元声明的名称与文件的基名一致时才能编译,而生成器过去是用输入文档来命名单元的,于是从 petstore.json 生成 MyClient.pas 会得到一个名为 petstore 的单元,根本无法编译。现在以输出文件名为准。
它知道自己在读哪个版本
OpenAPI 3.0 和 3.1 对同名关键字的定义并不一致,而旧的解析器把每份文档都当作 3.0 来处理。最明显的例子是 exclusiveMinimum,它在 3.0 中是 minimum 上的布尔修饰符,在 3.1 中则是一个独立的数值。把其中一种当成另一种来读,边界就会出错。
现在版本会被解析成一种方言,通过 Dialect、DialectMajor 和 DialectMinor 暴露出来,每个存在差异的关键字都会按照其所属版本的要求来读取。
oParser.ReadFromFile('api.yaml');
case oParser.Dialect of
oapiDialect30: ShowMessage('OpenAPI 3.0');
oapiDialect31: ShowMessage('OpenAPI 3.1');
oapiDialect32: ShowMessage('OpenAPI 3.2');
end;
除此之外,3.1 带来了 webhooks、jsonSchemaDialect 和 components.pathItems,许可证的 identifier,mutualTLS 安全方案,以数组形式声明的类型,例如 ["string","null"],以及声明为纯布尔值的 schema。这些现在全部支持。代码生成器尚未处理的 JSON Schema 2020-12 关键字会被读入模型,并通过 Warnings 报告出来,这样缺口是可见的,而不是隐形的。
在 3.2 方面,解析器支持 query 操作和 additionalOperations 映射。声明了其中任何一个的路径现在都会生成对应的方法,以 POST 发送并携带 X-HTTP-Method-Override 头。
路径级参数
这是大多数用户最能切身感受到的修复。规范允许你在路径项上声明一次参数,而不必在下面的每个操作里重复声明,这正是规范推荐的写法,也是大多数公开文档采用的写法。旧的解析器读取了这些参数,然后又把它们丢掉了。
生成的方法完全没有参数,请求发出时 URL 里还留着占位符,字面就是 /pets/{petId}。在第一次调用返回 404 之前,它看起来就像一个可用的客户端。
能够组合的 Schema
组合是旧解析器最薄弱的部分,而且每个分支都以不同的方式出错。allOf 在多个基础 schema 中只保留最后一个,丢弃了其余的成员。oneOf 把所有分支合并进同一个类,产生了重复字段。anyOf 根本没有处理,直接解析成字符串。同时声明了 properties 和 additionalProperties 的 schema 会丢失它的全部属性。
现在这四种都会按照文档所描述的那样生成。内联的对象 schema 也会得到属于自己的类,而不是退化成字符串,items 会作为完整的 schema 读取,因此内联对象数组、枚举数组和嵌套数组都能生成正确的类型。
携带服务器所需取值的枚举
生成的枚举表过去存放的是经过清理的 Pascal 标识符,而不是传输时使用的值,于是 allow-all 发出去变成了 allowall,json-file 变成了 jsonfile。凡是基于这些枚举构建的请求都会被拒绝。
现在这些表携带真实的取值,规范中的声明顺序得以保留,整数枚举也会生成对应的表,此外还会生成一个额外的 Unknown 成员,这样服务器日后新增的取值就不会被悄悄解码成列表里的第一个成员。
属性名从另一个方向得到了同样的处理。以 Delphi 保留字命名的 schema 属性,例如 property、class、string 或 function,又或者两个仅大小写不同的属性,例如 Name 和 name,过去会产生无法编译的单元。现在属性会被重命名,同时用 JSONName 特性保留传输时的名称,因此序列化结果仍与文档一致。
响应,包括那些你只声明一次的
default 响应以及 2XX、4XX 和 5XX 这类范围响应过去会被悄悄丢弃。一个只通过 default 声明错误的 API,这种做法很常见,生成出来的客户端完全没有类型化的错误。现在它们会被读取。当声明了多个成功响应时,会采用其中最小的那个,而当一个操作提供多种媒体类型时,优先选择 application/json。
传输中的参数
生成的客户端现在支持 cookie 参数,以及完整的 OpenAPI 参数序列化规则:matrix、label、simple、form、spaceDelimited、pipeDelimited 和 deepObject,每一种都支持 explode 和 allowReserved。新增的 AddArray 和 AddObject 方法可以在你需要时手工构建这些结构化的值。
// query parameters built with the serialization style the document declares
oRequest.AddArray('tags', ['dog', 'cat'], True); // explode
oRequest.AddObject('filter', ['color', 'red', 'size', 'M']);
外部引用
跨多个文件拆分的规范此前几乎无法工作。带有 JSON Pointer 片段的引用,例如 ./common.yaml#/components/schemas/Error,无法被解析。两个互相引用的文件会让解析器崩溃。子文档内部的相对引用是相对根文档解析的,而不是相对它自己所在的文件。两个基名相同的外部文件会互相覆盖,甚至可能替换掉属于主文档的 schema。而引用链只会被跟踪整整一步。
这些问题全部已修复,同时有一处是刻意收紧的:外部引用以前可以读取机器上的任意文件,包括 ../../../credentials.json,并把内容复制进生成的单元。现在外部引用被限制在主文档所在的目录之内。当某种目录布局确实需要越出该目录时,可以显式解除这一限制。
uses
sgcOpenAPI_Bundle;
begin
// off by default: references may not leave the folder of the main document
sgcOpenAPIAllowRefsOutsideRoot := True;
end;
并不完全是 UTF-8 的文件
RFC 8259 规定 JSON 文档采用 UTF-8,而大量已发布的规范并非如此。带有字节顺序标记的文件,只要包含任何 ASCII 之外的字符,过去就会立刻以 UTF-8 错误被拒绝,而中文或日文文本会被悄悄替换成问号。
不是有效 UTF-8 的文档现在会按 Windows-1252 读取并记录一条警告,而不是直接失败。带有字节顺序标记的 UTF-16 文件能够被正确读取。生成的文件会以显式指定的编码写出,目标编码无法表示的字符会被报告出来,而不是悄悄变成问号。
可以放进构建脚本的命令行
命令行现在会设置退出码:成功为 0,各种不同的失败分别为 1 到 7,因此构建步骤能够判断生成是否成功。错误消息始终写到标准错误,而 -l 开关现在只用于进度日志。
sgcOpenAPI -i petstore.json -o PetStoreClient.pas -c TPetStoreClient
if errorlevel 1 (
echo OpenAPI generation failed with exit code %errorlevel%
exit /b %errorlevel%
)
随之修复的还有三个命令行缺陷。文档中记载的 -output 开关会把单元写入当前目录下一个名为 utput 的文件,而由于消息被抑制,这次运行看上去仍然是成功的。在没有附加控制台时工具根本什么都不做,而计划任务或构建代理上恰恰就是这种情况,并且已有的输出重定向会被丢弃。还有,在未激活的机器上 -h 打印的是许可证错误而不是用法说明,-m 或 -a 的无效取值会被静默接受,未知的开关会被忽略。
本次发布新增的 -r(或 -remote)通过 converter.swagger.io 上的公共转换服务来转换 YAML 或 Swagger 2.0 文档。它默认关闭,因为这会把你的文档发送给第三方,所以需要你在知情的前提下主动开启。
Swagger 2.0 转换本身有两处值得一提的问题。每个数字都变成了字符串,于是数值型的默认值会产生无法编译的单元,转换后的文档也不是有效的 OpenAPI 3.0。另外,Swagger 2.0 的 discriminator 在那里是一个普通字符串,它会以无效的类型转换中止整个解析过程。
破坏性变更
有五项变更需要你做出决定,而不只是升级了事。
生成的客户端现在会校验服务器证书。以前不会,这意味着它们接受任何证书,包括中间人出示的证书。若要连接自签名或测试端点,需要有意识地把它关掉。
oClient.TLSOptions.VerifyCertificate := False; // test endpoints only
// certificates are trusted through the OpenSSL default paths, so a machine
// with no certificate store configured needs an explicit root
oClient.TLSOptions.RootCertFile := 'cacert.pem';
请求体采用 UTF-8。正如 RFC 8259 所要求的。类现在会把空字符串序列化为 "field": "",而不是把它省略。空值则由单独的选项控制。
oClient.JSONIgnoreEmptyStrings := True; // previous output
oClient.JSONIgnoreNullValues := True; // default
响应不再释放由你提供的 ResponseStream。把 OwnsResponseStream 设为 True 可恢复旧的行为。在客户端自身的 OnResponse、OnError 或 OnCancel 处理程序内部释放该客户端,现在会抛出一个明确的错误,而不是挂起。
命令行开关的取值必须写成 -name value 或 -name:value。没有分隔符的紧接写法,例如 -x"GET /pets",不再被接受。也正是这种写法使得 -x 会匹配到其他以 x 开头的开关,例如 -xml。
声明为数组的参数会生成为数组。它过去被生成为字符串,因此这些操作所生成方法的签名会发生变化。
其他所有内容
剩下的修复属于那种只有被咬到才会注意到的类型。JSON 类型出乎意料的成员,例如 "properties": [],过去会以无效的类型转换中止解析,而不是被跳过。没有默认值的 integer 类型 schema 会被赋予 0 作为默认值,而单值枚举会被当作常量处理,这会把该参数从生成的方法中彻底移除。把同一份文档读取两次会让每个路径、标签、服务器和 schema 都重复一遍。放在 paths 之中的规范扩展,例如 x-tagGroups,会被当作一个路径来读取。enum、required 和 tags 是用一个逗号分隔的文本辅助函数解析的,因此含有逗号或 JSON 转义的取值会被切断或损坏。列出多个方案的安全需求只保留了第一个,从而丢失了必须同时满足所有方案这一要求。info.contact 和 info.license 因为一个永远不可能为真的判断而从未被读取过。带有多个变量的服务器 URL 会代入错误的取值,还可能引发列表索引错误。打包规范会覆盖输入文件,既没有备份也没有任何提示,并且删除文档中所有排版用的撇号。还有,存放在含空格路径下的规范,例如 C:\My Specs\,无法解析它的外部引用。
如何获取
sgcOpenAPI 2026.9.0 现已发布,提供完整源代码和一年的更新。它支持 Delphi 7 到 Delphi 13 Florence,以及对应的 C++ Builder 版本。
有问题或建议?联系我们,你会收到来自编写这些代码的人的回复。
