Swagger/OpenAPI Client Generator for Delphi and FPC

news/2024/9/17 3:07:59

Delphi和FPC的Swagger/OpenAPI客户端生成器 Swagger/OpenAPI Client Generator for Delphi and FPC

Swagger/OpenAPI 是一种用于描述和定义RESTful API的规范和工具集。具体来说,它们提供了以下关键特性和作用:

一、定义与背景

  • Swagger :最初是一种用于描述RESTful API的规范,它允许开发者以一种标准化的方式定义API的请求、响应、参数、错误码等信息,使得API的使用和理解变得更加容易。
  • OpenAPI :作为Swagger的后继者,OpenAPI规范由OpenAPI Initiative(OAI)开发和维护。OpenAPI扩展了Swagger的功能,提供了更加丰富的API描述能力和更好的兼容性。现在,Swagger通常被视为OpenAPI的同义词,特别是在讨论API描述和文档生成时。

二、正文

OpenAPI(前身为Swagger)是一套规范,用于将服务器API端点的定义编码为文本,主要是JSON格式。

根据此参考文本,你可以生成多种语言的客户端代码以访问该服务。

img

在代码生成方面,Delphi似乎远远落后于其他语言。对于FPC(Free Pascal Compiler),我更是未找到任何可用的解决方案。

由于我们在Tranquil IT的内部工具中需要此功能,因此我们发布了新的mormot.net.openapi.pas单元,这可以说是一个改变游戏规则的举措。感谢Andreas启动了这个项目,并在早期阶段对其进行了测试!

当前解决方案

在网上快速搜索关于Delphi和FPC的Swagger或OpenAPI代码生成器时,我确实感到失望。

Paolo Rossi为Delphi发布了一个OpenAPI,但它并不是一个代码生成器,而是一个OpenAPI规范解析器和发射器。因此,这并不是我们想要的。

有一个闭源替代方案,但根据我在演示视频中看到的内容,其价格(360欧元!)似乎过高,不予考虑。

Ali Dehbansiahkarbon发布了他的OpenAPIClientWizard仓库,该仓库仍处于测试阶段,并且只具有最基本的功能(路径提取)。

来自TMS的杰出开发者Wagner Landgraf发布了他的OpenAPI-Delphi-Generator项目,这是Delphi中最先进的尝试。

但是,它似乎缺少一些基本功能,如allOf支持或适当的错误处理。而且,它仅适用于最新版本的Delphi,大量使用泛型,并且生成器依赖于第三方的专有库。

因此,目前还没有我们想要的解决方案。

如果你知道在互联网的某个深处还有另一个被遗漏的库,请不吝告知你的反馈。

OpenAPI 邂逅 mORMot
事实上,在我们的开源mORMot框架中,我们拥有创建此类客户端生成器所需的所有工具,尤其是:

  • 非常强大的RTTI缓存,具有对高级数据结构进行自定义JSON序列化的功能;
  • 我们所需的所有JSON解析和文本生成工具,这些工具具有极富表现力的定义(无需继承类或为类添加冗长的属性);
  • 多个HTTP客户端类,可在所有支持的平台和编译器上运行;
  • 一个已经与FPC和Delphi兼容的库,甚至可以与最旧的Delphi版本(如Delphi 7或2007)配合使用,这些版本仍被用于长期存在的生产项目。

由于我们手头拥有所有这些基本工具,因此仅一个mormot.net.openapi.pas单元就足以为我们完成所有繁重的工作。

img

再次感谢Tranquil IT IT允许将此工具作为mORMot的一部分发布!

Main Features

以下是我们的Delphi和FPC的OpenAPI代码生成器的主要功能:

  • 使用高级Pascal记录和动态数组表示“对象”DTO(数据传输对象)和“数组”值
  • 使用高级Pascal枚举和集合表示“枚举”值
  • 将HTTP状态错误代码转换为高级Pascal异常
  • 识别相似的“属性”或“枚举”以重用相同的Pascal类型
  • 支持对象、参数或类型的嵌套“$ref”
  • 支持“allOf”属性,具有适当的属性继承/重载
  • 支持“oneOf”属性,用于字符串或备用记录类型
  • 支持“in”:“header”和“in”:“cookie”参数属性
  • 对于“oneOf”或“anyOf”JSON值,回退到变体Pascal类型
  • 每个方法执行都是线程安全和阻塞的,以确保安全性
  • 生成的源代码单元非常小且易于使用、阅读和调试
  • 可以在单元源代码中生成非常详细的注释文档
  • 可调引擎,具有大量生成选项(例如,关于详细程度)
  • 利用mORMot的RTTI和JSON内核进行其内部处理
  • 与FPC和旧版Delphi(7-2009)兼容
  • 已经过多个Swagger 2和OpenAPI 3参考内容的测试,但欢迎您提供输入,因为它并不完全兼容!

生成选项确实可以根据您的实际需求调整输出:

  /// 允许自定义TOpenApiParser过程// - opoNoEnum 禁用任何Pascal枚举类型生成// - opoNoDateTime 禁用任何Pascal TDate/TDateTime类型生成// - opoDtoNoDescription 不为DTO生成Description注释// - opoDtoNoRefFrom 不为DTO生成'from #/....'注释// - opoDtoNoExample 不为DTO生成'Example:'注释// - opoDtoNoPattern 不为DTO生成'Pattern:'注释// - opoClientExcludeDeprecated 移除任何标记为已弃用的操作// - opoClientNoDescription 仅为客户端生成最小注释// - opoClientNoException 不会生成任何异常,而是回退到EJsonClient// - opoClientOnlySummary 将减少操作注释的详细程度// - opoGenerateSingleApiUnit 将使GenerateClient返回一个单独的{name}.api单元,其中包含所需的DTO和客户端类// - opoGenerateStringType 将生成普通字符串类型而不是RawUtf8// - opoGenerateOldDelphiCompatible 将为Delphi 7/2007/2009兼容性生成一个void/dummy托管字段,并避免'T... has no type info'错误// - 例如,参见OPENAPI_CONCISE,用于生成单个单元、简单且未记录的输出TOpenApiParserOption = ( ...

当然,您的客户端代码中需要一些基本的mORMot单元。该工具不会生成一个“纯Delphi RTL”客户端。但公平地说,早期的Delphi中没有JSON支持,而且维护编译器和RTL版本之间的差异,尤其是关于JSON、RTTI、HTTP的差异,最终将像是在重新发明mORMot。我们只需要使用有效的工具。

请注意,生成的客户端代码完全不依赖于mORMot的其他功能,如ORM或SOA。它与这些功能完全分离,尽管这些功能非常强大,但有时也会令人困惑。使用客户端代码时,您将使用mORMot,但几乎不会注意到它的存在。这只啮齿动物将躲在它的洞里。但如果您需要它,例如用于添加日志或服务,它将很乐意帮助您。😃

Enter the PetStore

最著名的OpenAPI示例是著名的“Pet Store”样本。

你可以在“petstore.swagger.io”上找到整个API的网页预览。

img

此API在JSON文件中定义,该文件可在本要点中找到。

然后我们可以编写这个小项目:

program OpenApiPetStore;
usesmormot.core.base,mormot.core.os,mormot.net.openapi;varp : TOpenApiParser;
beginp := TOpenApiParser.Create('PetStore');  // 创建OpenAPI解析器实例tryp.Options := [];  // 设置选项为空p.ParseFile(Executable.ProgramPath + 'petstore.swagger.json');  // 解析指定路径的swagger.json文件p.ExportToDirectory(Executable.ProgramPath);  // 导出解析结果到指定目录finallyp.Free;  // 释放解析器实例end;
end.

注意,如果你更喜欢,很快就会有一个独立的命令行工具用于生成。

使用默认选项,我们会得到两个单元,一个包含数据传输对象(DTOs),另一个包含实际的客户端类。

你可以在这个要点gist.中看到结果。

以下只是一个简单的方法定义:

    // getUserByName [get] /user/{username}//// 摘要:通过用户名获取用户//// 参数:// - [path] Username (required): 需要获取的用户名。使用user1进行测试。//// 响应:// - 200 (main): 成功操作// - 400: 提供了无效的用户名// - 404: 未找到用户function GetUserByName(const Username: RawUtf8): TUser;

TUser记录将用作方法响应的高级结果记录。如果mORMot的 RawUtf8=Utf8String类型不符合你的需求,你可以定义一个选项来生成纯 String值。

你可以观察到dto单元只有少数依赖项,因此你可以在你的业务逻辑代码中使用它,而不会受到客户端单元的“逻辑污染”。
实际的DTOs数据结构被定义为记录,因此它们不需要任何create/free,并且可以轻松地使用。一些枚举是从原始Petstore JSON定义中指定的字符串值列表中生成的。这使得你的客户端代码非常可读,并且防错,因为你无法向服务器发送任何未经验证的值。

客户端的“魔法”是在客户端单元中的一个名为 TPetStoreClient的包装类中完成的。
每个方法定义都遵循预期的规范,并且具有从原始JSON规范的描述字段生成的非常准确的注释。如果你觉得它太冗长,你可以包含 opoClientNoDescription选项。方法按“标签”分组,在OpenAPI术语中,这是一种按主题聚集方法的方式。这反映在代码顺序以及注释中。

如果我们定义以下选项:

  p.Options := OPENAPI_CONCISE;

然后会生成一个几乎没有内部文档的单元。
你可以在这个要点中看到这个单元。

    // 存储方法function GetInventory(): variant;function PlaceOrder(const Payload: TOrder): TOrder;function GetOrderById(OrderId: Int64): TOrder;procedure DeleteOrder(OrderId: Int64);

如果JSON规范没有像上面的GetInventory()那样的实际回答布局,我们无法生成像TOrder这样的DTO。
因此,我们回退到一个变体,它可以包含服务器响应的RTTI反序列化后的任何JSON输入:一个字符串、一个整数,或者更可能是一个复杂的对象或数组,编码为强大的mORMot TDocVariant自定义变体类型。在未来,如果你更喜欢,我们可以通过适当的选项生成IDocList和IDocDict实例 - 欢迎反馈。

你可能已经注意到,生成的代码非常干净,尤其是与替代解决方案实际生成的内容相比。它是一个很好的展示,展示了如何编写mORMot代码,具有跨平台RTTI注册和JSON自定义序列化等高级功能。

More Complex APIs

更复杂的API

在我们的测试和验证过程中,我们使用了一些更复杂的API定义。

例如,我们内部使用了一个Ultravisor服务,其单文件API代码可以在此处查看。它包含大量的DTO(数据传输对象)和方法。当规范中的多个位置实际元素匹配时,就会生成并重用一些枚举。即使我们没有在此OPENAPI_CONCISE中包含所有可用文档(出于安全原因,关于在博客上发布有关内部API的详细信息),生成的客户端单元仍然非常易于阅读。

你可能会注意到,它还定义了一些 Exception classes,以便生成器能够将实际的HTTP错误代码(例如401、403...)映射到真实的Pascal异常 Exception,并附带它们自己的结果集DTO。如果API执行成功,其客户端方法就会按预期执行,并返回输出值,就像常规的本地代码一样。但如果服务器返回错误代码,客户端代码就会拦截它,将其映射到设计的异常类 Exception class,并最终引发异常,同时在其 Error属性中包含所有附加数据:

constructor EValidationErrorResponse.CreateResp(const Format: RawUtf8;const Args: array of const; const Resp: TJsonResponse);
begininherited CreateResp(Format, Args, Resp);  // 调用继承的构造方法LoadJson(fError, Resp.Content, TypeInfo(TValidationErrorResponse));  // 加载JSON响应内容到fError
end;(...)procedure TUltravisorClient.OnError1(const Sender: IJsonClient;const Response: TJsonResponse; const ErrorMsg: shortstring);
vare: EJsonClientClass;
begincase Response.Status of  // 根据响应状态码选择异常类400:e := EValidationErrorResponse;401:e := EUnauthorizedResponse;403:e := EForbiddenResponse;404:e := EResourceNotFoundError;422:e := EIntegrityErrorResponse;elsee := EJsonClient;end;raise e.CreateResp('%.%', [self, ErrorMsg], Response);  // 引发异常,并传递响应信息
end;

这样,你就可以在客户端以非常自然的方式处理API错误,所有必要的信息都包含在高级Pascal代码中,通过标准的try ... except on E: E#### do ... 块进行处理。

生成器还会正确记录HTTP错误代码和 Exception classes的映射,例如以下代码片段所示:

// post_account_res_add_grant_auth [post] /accounts/{uuid}/add-grant-auth/
//
// 摘要:授予用户对帐户进行授权许可的权限
// 描述:
//   角色:对于vm对象为vm_admin,否则为templates
//
// 参数:
// - [path] Uuid (必需):管理程序uuid
// - [body] Payload (必需)
//
// 响应:
// - 200 (main):成功
// - 400 [EValidationErrorResponse]:参数格式或类型无效
// - 401 [EUnauthorizedResponse]:用户未认证
// - 403 [EForbiddenResponse]:用户权限不足
// - 404 [EResourceNotFoundError]:未找到管理程序
// - 422 [EIntegrityErrorResponse]:参数格式有效但与服务器状态不兼容
function PostAccountResAddGrantAuth(const Uuid: RawUtf8; const Payload: TUserShort): TDbAccount;

另一个API示例来自Paolo Rossi的参考材料。你可以在这个要点gist中找到其JSON规范、DTO和客户端单元,以及其单个API单元。

以下是生成的一个方法及其实现的摘录:

// sign_delete [delete] /scope/{job}
//
// 描述:
//   删除一个验证作业
//
// 参数:
// - [path] Job (必需):作业ID(20个字符)
//
// 响应:
// - 200 (main):成功删除
// - 404 [EError]:未找到作业 `unknown-job`
// - default [EError]
function SignDelete(const Job: RawUtf8): TDtoAuth14;(...)function TAuthClient.SignDelete(const Job: RawUtf8): TDtoAuth14;
beginfClient.Request('DELETE', '/scope/%', [Job], [], [],result, TypeInfo(TDtoAuth14), OnError1);  // 发送DELETE请求,并处理响应或错误
end;

这是我们代码生成器的一个很好的示例,它利用了mORMot的核心功能。而且这段代码可以在非常老的Delphi 7上运行!

Feedback is Welcome

当然,我们并没有完全实现所有OpenAPI v3.1规范。因此,如果你对这个生成器有任何问题,欢迎在我们的论坛上报告你的问题report your problematic JSON on our forum,,我们会尽力做出适当的安排。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/57850.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

数据包格式

近来常思,不应止步于此,可自觉进阶缓慢,一筹莫展,就打算自废武功复习一下,那就从状态码开始吧。前言近来常思,不应止步于此,可自觉进阶缓慢,一筹莫展,就打算自废武功复习一下,那就从状态码开始吧。 由于强迫症患者,所以后面就顺便把数据包格式啥的都一起写一下吧。请…

英特尔FPGA深度学习加速(DLA)套件

英特尔FPGA深度学习加速(DLA)套件英特尔FPGA的DLA加速套件,如图11-17所示。图11-17 英特尔FPGA的DLA加速套件 深度学习部署工具包(DLDT)中的推理引擎,提供了一个高级的设备无关API来编程推理。这是一些示例代码,如图11-18所示。图11-18 深度学习部署工具包(DLDT)中的推…

推理引擎流程

推理引擎流程 总结一下推理引擎(IE)调用FPGA设备的流程。开发人员通过IE通用API进行推理调用,IE调用FPGA插件,这调用了运行OpenCL运行时的DLA(英特尔深度学习加速器)。最终发送到实现基元(如卷积、ReLU等)的DLA FPGA IP。如图11-28所示。图11-28 推理引擎(IE)调用FPG…

企业管理系统-ERP开发

Enterprise Resource Planning 基于.NET FW 4.8.1开发的ERP系统,以 HandyControl 作为设计参考。 目的 初衷在于学习C#开发。自己设定了一个学习的目标,朝着WPF的方向前进,开发一个能媲美于公司管理系统的Windows客户端(前公司的企业管理系统使用的是Office Access VBA开发…

Exception in thread main java.io.IOException :could not find resource xxxxx.xml

错误如下: 错误原因:(无法正确识别项目中的Resources目录或者java目录的配置文件) 1. resource不是资源目录了 2.配置文件在java目录下 或者这样 解决方法: 1. 在项目结构中将resource选择为资源文件 2. 查看pom文件的build ,如果指定了资源文件是java目录而忘记了指定re…

24.9.7——小学期开发实记

今天完成了基础信息的CRUD,但是遇到了一个关于JAVA Spring Boot注入的问题。 问题如下: Error:(20, 34) Could not autowire. No beans of workCenterInfoMapper type found.@Autowired private workCenterInfoMapper workCenterInfoMapper; 我改成:@Resource private workC…

2024软件工程第一次个人作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/SE2024/这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/SE2024/homework/13243这个作业的目标 初步认识博客园和GIthub平台,初步了解软件工程学科的任务学号 102201622一、个人logo文生图任务 使用工具:Op…

Gitness 基础安装

对gitness最基本的安装以及从Github配置token获取源码仓库的相关配置。目录Docker 安装注册账户创建项目导入已有仓库配置 Github Token同步源代码仓库 官方链接Gitness was the next step in the evolution of Drone, from continuous integration to source code hosting, br…