#json-schema #openapi #schema-validation #codegen #preprocessor #schema-file #template

schematools

用于json-schema和openapi规范代码生成、预处理和验证的工具

14个版本 (7个破坏性更新)

0.18.2 2024年6月3日
0.18.1 2024年1月17日
0.18.0 2023年12月17日
0.15.2 2023年10月30日
0.13.1 2023年3月29日

#199 in Web编程

Download history 2/week @ 2024-05-01 1126/week @ 2024-05-08 623/week @ 2024-05-15 232/week @ 2024-05-22 680/week @ 2024-05-29 207/week @ 2024-06-05 389/week @ 2024-06-12 463/week @ 2024-06-19 72/week @ 2024-06-26 23/week @ 2024-07-03 78/week @ 2024-07-10 79/week @ 2024-07-17 147/week @ 2024-07-24 93/week @ 2024-07-31 135/week @ 2024-08-07 76/week @ 2024-08-14

每月下载量 480次
用于 schematools-cli

MIT 许可证

355KB
10K SLoC

Schema Tools

build tests

简介

这只是另一种OpenAPI/jsonschema代码生成方法的尝试。这是一个用 Rust 编写的家庭项目,一个简单的一站式控制台工具,具有以下功能:

  • openapi/json schema验证
  • schema预处理
    • 取消引用
    • 合并-allof
    • 修补(应用/生成json-patch)
    • 名称
    • 合并-openapi
  • Tera (jinja2) 代码生成器,支持自定义模板

它旨在加快使用json对象(包括API级别的json schemas以及事件)的微服务开发。

与其他解决方案(如 openapi-generator)在方法上的主要区别

  • 更健壮的模板语言,如jinja2
  • 将特定于语言的逻辑移动到模板中,包括类型映射、保留词
  • 无需区分器即可包装混合类型
  • 一个工具用于客户端、服务器、json-schema队列消费者以及处理openapi
  • 每个项目的构建中都使用相对较小的二进制文件
  • 按照微服务方法执行代码生成(而不是作为独立的通用客户端库)
  • 支持json-schema注册表,TODO:共享模型 - 创建一个模型用于不同客户端/服务器中共享的结构,以避免映射相同结构

一般规则

  • 所有命令都支持yaml和json文件。
  • 使用帮助获取可用参数列表 schema-tools process --help
  • -v-vv-vvv-vvvv 详细程度级别

验证

验证openapi规范

schematools validate openapi openapi.yaml

验证json schema定义

schematools validate openapi schema.yaml

这两个命令在失败的情况下都会返回非零退出码。错误报告不是很清晰,但它显示了不满足json模式的位置。待办事项:解决这个问题 问题

过程

常用CLI参数

<file>                      Path to json/yaml file with openapi specification
-o, --output <output>       Returned format [default: json] [possible values: json,  yaml]
--to-file <to-file>         Path of output file, default output to stdout

命名

如果你的openapi规范遵循RESTFUL openapi规则,你可以创建缺失的json-schema标题或尝试重命名现有端点的operationId

schematools process name schema.yaml

其他选项包括

--overwrite                  Should overwrite existing titles
--overwrite-ambiguous         Should overwrite ambiguous titles
--resource-method-version    Reverts order of operationId generator to resource+method+version

引用

要替换openapi中所有出现的$ref,你可能需要输入

schematools process dereference schema.yaml

你应该很少进行完整的openapi引用。你可以使用以下选项部分引用模式

  • skip-references - 如果你在使用通用schema的注册表,这很有用。在许多情况下,你不想解引用这样的$ref
  • skip-root-internal-references - 有助于跳过存储在根openapi文件中的非常常见的/components.* openapi schema引用
  • create-internal-references - 节省空间,每个指针只解引用一次,所有后续出现都替换为第一个指针的引用
 --create-internal-references             Creates internal references if refs where pointing to same place
--skip-root-internal-references           Leaves internal references intact in root schema file
--skip-references <skip-references>...    List of hostnames to skip dereference

合并所有

allOf合并到对象类型

schematools process merge-all-of openapi.yaml

在考虑json模式更多是表示验证而不是数据结构本身的情况下,在进行代码生成之前执行此操作是有用的。在许多语言中,联合体很复杂,但如果你使用allOf来提取结构体的公共部分,这可能是一个非常有用的功能。

修补

如果你收到的openapi看起来是破损的,你可以修复它并创建一个json-patch文件

schematools process patch <file> create <original-file> 

然后你可以在处理过程中将这样的补丁应用到原始的openapi文件上

schematools process patch <file> apply <patch-file> 

合并openapi和增加

如果你的微服务被分割成多个服务(并且在同一个ingress下暴露),创建一个openapi定义可能很有用

schematools process merge-openapi <file> --with <with>

一些有用的选项,可能需要用于合并openapi的版本控制

--add-version <add-version>    Should add info.x-version- attribute to openapi specification
--retag <retag>                Should change tags of all endpoints of merged openapi

要增加合并的openapi版本,你可以使用这个命令

schematools process bump-openapi <file> --original <previous-version-file>

它应该根据所有子openapi semversions正确更改openapi的版本

代码生成openapi

代码生成本身是通过处理模板目录来执行的。在完成之前,所有来自openapi/json-schema文件的数据都必须提取和处理。有两种执行代码生成的方式

  • schematools codegen json-schema json-schema.json [...]用于处理json schema文件。如果json schema的标题缺失,则需要额外的属性--base-name <base-name>。一个json-schema不一定意味着代码生成运行的输出将正好是一个结构体/对象/类 - 在复杂的json模式的情况下,它将生成多个模型。
  • schematools codegen openapi openapi.json [...]用于处理openapi规范 - 提取端点和模型。

简单用法

schema-tools codegen openapi openapi.json --template templates/  --target-dir pkg/client/
  • openapi.json - openapi规范文件
  • --template templates/ - 包含jinja2文件的目录
  • --target-dir pkg/client/ - 代码应该生成的位置

代码生成选项

  • --nested-arrays-as-models - 一些语言允许创建内联类型 Vec<HashMap<Vec<HashMap>>>> / [][][]int,有些可能需要为这种情况创建包装类型
  • --optional-and-nullable-as-models - openapi 允许创建两级“空值”,某些语言不区分 null 和 undefined。此选项将所有 nullable 和 optional 字段的实例包裹在单独的类型中
  • --wrappers - 选项用于将混合类型(oneOf)包裹为具有自定义反序列化逻辑的自定义对象
  • -o <options> - 选项将选项(字符串或 json)传递到所有模板文件,例如 -o 'name=ordersClient' -o 'usedEndpoints=["/orders", "/orders/{id}/items"]'
  • --format - 在代码生成后执行语言格式化器,例如 --format "gofmt -w"

代码生成模板

使用 --template templates/ 选项指定代码生成模板目录。该目录中的所有简单文件都会复制到 --target-dir 目录下,除了 .j2 模板。其中最重要的是每个 .j2 的头部(文件的第一行)。

models.j2 示例

{# type=models,filename=models.go #}

/* options: {{ options | json_encode(pretty=true) }} */
/* models: {{ models | json_encode(pretty=true) }} */

endpoints.j2 示例

{# type=endpoints,filename=endpoints.go #}

/* options: {{ options | json_encode(pretty=true) }} */
/* models: {{ endpoints | json_encode(pretty=true) }} */

头部 决定了如何处理模板文件,如何以及何时生成文件。头部选项

  • type=? - 可能的值:endpointsmodels
  • filename=? - 创建的目标文件路径。可以与选项混合使用,例如 filename=clients/%options.name%/endpoints.go
  • if=foo:bar - 使用模板文件的条件。应与选项混合使用,例如 if=%options.type%:server

有关如何编写模板文件的更多信息,请参阅 Tera 文档。要获取我们创建的附加过滤器的列表,请访问 filters.rs

代码生成模板继承

代码生成允许定义多个 --template 选项。

schematools codegen openapi.json --template dir1/ --template2 dir2/ --target-dir output/

按顺序逐个加载所有目录的文件,并在发生冲突时覆盖它们。还可以指定注册表,目前它只能是一个 git 仓库

schematools codegen openapi.json --template REGISTRY::dir1/ --template2 dir2/ --target-dir output/

可用的代码生成模板

待办:将代码生成模板推送到独立的仓库,并在那里编写示例

这正是这个工具的全部意义。它将所有现有功能包装在一起,并为openapi处理添加全局应用程序上下文。

schematools chain -vvvv \
   -c 'process merge-all-of --leave-invalid-properties https://domain.com/openapi/orders/api.yaml' \
   -c 'process name - --resource-method-version --overwrite' \
   -c 'validate openapi - --continue-on-error' \
   -c 'codegen openapi - \
        --template codegen/client/ \
        --format "gofmt -w" \
        --target-dir pkg/client/ \
        -o namespace=orders \
        -o clientName=OrdersClient'

所有命令都接受与单独执行时相同的参数。唯一的区别是,第一次执行必须将真实的模式文件作为 -f 参数。接下来的执行应使用 - 使用之前生成的模式文件。

schematools chain -vvvv \
   -c 'process merge-all-of --leave-invalid-properties specifications/api.yaml' \
   -c 'process name - --resource-method-version --overwrite' \
   -c 'validate openapi - ' 
   -c 'codegen openapi - \
        --template codegen/server/ \
        --format "gofmt -w" \
        --target-dir internal/http/ \
        -o namespace=myservice'

在链式处理期间,可以使用 output 命令将处理后的模式导出到文件中

schematools chain -vvvv \
   -c 'process merge-all-of --leave-invalid-properties specifications/api.yaml' \
   -c 'process name - --resource-method-version --overwrite' \
   -c 'validate openapi - ' \
   -c 'output --to-file=test.json -o json' \
   -c 'codegen openapi - \
        --template codegen/server/ \
        --format "gofmt -w" \
        --target-dir internal/http/ \
        -o namespace=myservice'

注册表

有一个选项可以将单独的Git仓库作为模板的来源

schematools chain -vvvv \
   -c 'registry add common git://github.com/kstasik/schema-tools --tag v0.0.1' \
   -c 'process merge-all-of --leave-invalid-properties clients/client1.yaml' \
   -c 'process name - --resource-method-version --overwrite' \
   -c 'validate openapi - --continue-on-error' \
   -c 'codegen openapi - --template common::resources/openapi/ --target-dir pkg/client1/ -o namespace=client1 -o clientName=Client1'

要针对此类注册表,您只需使用: --template REGISTRY_NAME::path/

使用示例

此示例显示了具有两个外部openapi客户端依赖项的openapi http服务器

schematools chain -vv \
  # 0. Register external repository with templates and fix it to tag
  -c 'registry add default https://codegen-templates/templates.git --tag v0.5.0' \
  # 1. Load local openapi specification from file and dereference
  -c 'process dereference spec/api.yaml --skip-root-internal-references --create-internal-references' \
  # 1. Convert allOf to structs
  -c 'process merge-all-of - --leave-invalid-properties' \
  # 1. Overwrite titles of schemas and operationIds of endpoints in openapi
  -c 'process name - --overwrite --resource-method-version' \
  # 1. Perform validation of our openapi specification - interrupt build on error
  -c 'validate openapi - ' \
  # 1. Create models and routers
  -c 'codegen openapi - --template default::rust-actix-server/ --format "rustfmt --edition 2018" --target-dir src/app/ -o name=ShippingApp'  \
  \
  # 2. Load remote openapi definition of external service
  -c 'process dereference https://schemas.com/openapi/orders/v0.1.0.json --skip-root-internal-references' \
  # 2. Convert allOf to structs
  -c 'process merge-all-of - --leave-invalid-properties' \
  # 2. Overwrite titles of schemas and operationIds of endpoints in openapi because it follow restful standards
  -c 'process name - --overwrite --resource-method-version' \
  # 2. Patch openapi specification because it has an error and we don't want to wait for a fix to be published by other project
  -c 'patch - apply specs/fixes/orders.yaml' \
  # 2. Validate openapi definition but continue on failure because it's an external client not owned by project
  -c 'validate openapi - --continue-on-error' \
  # 2. Create client
  -c "codegen openapi - --optional-and-nullable-as-models --template default::rust-reqwest-http/ --format 'rustfmt --edition 2018' \
    -o 'usedEndpoints=~[\"ordersListV3\",\"ordersCreateV3\"]' \
    --target-dir src/clients/ -o name=OrdersClient" \
  \
  # 3. Load remote openapi definition of external service
  -c 'process dereference https://schemas.com/openapi/users/v0.1.0.json --skip-root-internal-references' \
  # 3. Convert allOf to structs
  -c 'process merge-all-of - --leave-invalid-properties' \
  # 3. Overwrite titles of schemas and operationIds of endpoints in openapi because it follow restful standards
  -c 'process name - --overwrite --resource-method-version' \
  # 3. Validate openapi definition but continue on failure because it's an external client not owned by project
  -c 'validate openapi - --continue-on-error' \
  # 3. Create client
  -c "codegen openapi - --optional-and-nullable-as-models --template default::rust-reqwest-http/ --format 'rustfmt --edition 2018' \
    -o 'usedEndpoints=~[\"usersListV3\",\"usersCreateV3\"]' \
    --target-dir src/clients/ -o name=UsersClient"

依赖项

~19–36MB
~666K SLoC