5 个版本
0.1.4 | 2023年9月25日 |
---|---|
0.1.3 | 2023年9月25日 |
0.1.2 | 2023年9月25日 |
0.1.1 | 2023年9月3日 |
0.1.0 | 2023年9月3日 |
#391 in 开发工具
41KB
799 行
Schema 解析器 & 代码生成器
一个不依赖于特定语言的模型生成器,用于 JSON schemas 的一个子集。(目前仅限 Rust,下一个计划:python + pydantic)
如何使用
查看 codegen-test
包以了解如何将其用于编译时生成代码。
Schema 约束
在编写 parser
和 deserializer
时,我添加了一些可能的 schemas 的约束,这仅允许 asyncapi
schema 定义的一个子集。
- 文档中的
components -> schemas
部分的每个顶级项都必须是实际的 schema,这不被允许
components:
schemas:
MyEntity:
$ref: '#/components/schemas/AnotherEntity'
- 每个顶级 schema 只能是以下之一:
[AllOf, OneOf, AnyOf, type: object]
,顶级数组类型目前不可用,在您的 asyncapi schema 中,我建议在规范的messages
部分创建匿名 schema,并在components/schemas
部分创建特定的item
类型,以便您的代码具有项目类型,并且可以通过包装在特定语言集合中来轻松反序列化有效负载。 - 当前枚举仅支持 String 值,即使在生成时的反序列化/解析中支持数值枚举,也会抛出错误,因为我尚未创建特定类型来区分它们和文字枚举。
- 每次指定
const
值时,都必须指定一个type
。 - 目前仅支持整数,任何
format
指令都简单地被忽略 - 由于当前
AllOf
的实现方式,重复属性会在 Rust 中引起错误,当前的代码生成器只是将合并后的模式取出来,为每个(如果设置了title
则为命名一个)创建一个AnonymousEntity
,然后通过在结构体中使用#[serde(flatten)]
来合并它们,如果合并后的模式定义了重叠属性,这将导致反序列化失败。(这个问题已经在我的计划中,但不是优先考虑的事项,在面向对象的语言中,我的代码生成器会简单地扩展所有AllOf
模式类,重复属性将由编程语言的继承来处理)
针对 Rust 用户的说明
- 对于设置了特定
discriminator
的OneOf
模式,它目前只有在判别器匹配实体的名称时才起作用(对于匿名实体,为确定性的名称设置title
属性),否则,如果您在const
字段中使用特殊值作为判别器,目前需要暂时省略discriminator
并仅使用生成的#[serde(untagged)]
枚举,使用const
字段将使用monostate
包来尊重。 - 当前
AllOf
模式不合并属性,出于简便性,它为内部模式创建结构体,然后将它们通过#[serde(flatten)]
合并到单个结构体中。(出于简便性,我可能会在其他语言中使用类似的解决方案,即有一个命名的空类从匿名/命名的结构体继承其字段)
计划
- 用于代码生成的 CLI 工具
- Python
pydantic
模型生成器 - Protobuf 生成器
问题
反序列化器定义了用于模式反序列化的 untagged
枚举,其中包含 monostate::MustBe
,当模式不匹配时,会导致相当无用的错误消息,大多数错误都是 Did not match any variant in SchemaDef
示例
Asyncapi 模式定义
RequestBase:
type: object
additionalProperties:
type: array
properties:
id:
type: string
description: "correlation id to match request and response"
kind:
type: string
const: request
tupleProp:
type: array
items: false
prefixItems:
- type: string
- type: object
required:
- id
GetUser:
description: TODO
allOf:
- $ref: '#/components/schemas/RequestBase'
- type: object
title: GetUserInner
properties:
data:
title: GetUserData
type: object
properties:
userId:
type: string
name:
type: string
required:
- userId
required:
- data
- event
DeleteUser:
description: TODO
allOf:
- $ref: '#/components/schemas/RequestBase'
- type: object
title: DeleteUserInner
properties:
data:
title: DeleteUserData
type: object
properties:
userId:
type: string
required:
- userId
required:
- data
- event
SampleRequestPayload:
description: "SampleRequestPayload"
discriminator: event
oneOf:
- $ref: '#/components/schemas/GetUser'
- $ref: '#/components/schemas/DeleteUser'
生成的 Rust 代码
#[derive(Debug, Clone, Eq, PartialEq, serde :: Deserialize, serde :: Serialize)]
pub struct RequestBase {
#[serde(rename = "id")]
id: String,
#[serde(rename = "kind")]
kind: Option<monostate::MustBe!("request")>,
#[serde(rename = "tupleProp")]
tuple_prop: Option<(String, serde_json::Value)>,
#[serde(flatten)]
additional_properties: std::collections::HashMap<String, Vec<serde_json::Value>>,
}
#[derive(Debug, Clone, Eq, PartialEq, serde :: Deserialize, serde :: Serialize)]
pub struct DeleteUserData {
#[serde(rename = "userId")]
user_id: String,
}
#[derive(Debug, Clone, Eq, PartialEq, serde :: Deserialize, serde :: Serialize)]
pub struct DeleteUserInner {
#[serde(rename = "data")]
data: DeleteUserData,
}
#[derive(Debug, Clone, Eq, PartialEq, serde :: Deserialize, serde :: Serialize)]
pub struct DeleteUser {
#[serde(flatten)]
request_base: RequestBase,
#[serde(flatten)]
delete_user_inner: DeleteUserInner,
}
#[derive(Debug, Clone, Eq, PartialEq, serde :: Deserialize, serde :: Serialize)]
pub struct GetUserData {
#[serde(rename = "userId")]
user_id: String,
#[serde(rename = "name")]
name: Option<String>,
}
#[derive(Debug, Clone, Eq, PartialEq, serde :: Deserialize, serde :: Serialize)]
pub struct GetUserInner {
#[serde(rename = "data")]
data: GetUserData,
}
#[derive(Debug, Clone, Eq, PartialEq, serde :: Deserialize, serde :: Serialize)]
pub struct GetUser {
#[serde(flatten)]
request_base: RequestBase,
#[serde(flatten)]
get_user_inner: GetUserInner,
}
#[derive(Debug, Clone, Eq, PartialEq, serde :: Deserialize, serde :: Serialize)]
#[serde(tag = "event")]
pub enum SampleRequestPayload {
GetUser(GetUser),
DeleteUser(DeleteUser),
}
依赖项
~5MB
~98K SLoC