Swagger/OpenAPI Client Generator for Delphi and FPC

news/2024/12/22 19:01:53/文章来源:https://www.cnblogs.com/hieroly/p/18402450

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.hqwc.cn/news/793901.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据包格式

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

英特尔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…

SPI

SPI SPI共用4种模式,通过控制CPOL(时钟极性)和CPHA(时钟相位)来控制,此处以W25Q128的时序图来利用IO口模拟SPI模式0与模式3通信过程中的时序。模式0:SCL处于低电平,第一个边沿收发 模式1:SCL处于低电平,第二个边沿收发 模式2:SCL处于高电平,第一个边沿收发 模式3:S…

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…

Gradle下载太慢? Gradle官方最全版本极速下载网址

Gradle下载太慢? Gradle官方最全版本极速下载地址 Gradle简介 Gradle 作为一种开源的构建工具,理论上可以开发所有应用,在 Java 应用程序的构建与发布方面起着极大的助力作用。Gradle能够为开发者构建应用程序提供有力支持,这不仅对自动化测试大有益处,还能实现分发构建,…

软件工程课程第一次个人作业1

这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/SE2024/这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/SE2024/homework/13243这个作业的目标 1.理解与运用AI辅助学习与工作(生成logo、指南等) 2.自我介绍 3.学习规划 4.熟悉学习环境学号 102202123一、…