15 个版本
0.1.0 | 2024年5月13日 |
---|---|
0.0.16 | 2024年2月28日 |
0.0.15 | 2023年12月15日 |
0.0.14 | 2023年9月25日 |
0.0.5 | 2021年11月17日 |
#2380 in Rust 模式
402,022 每月下载量
在 216 个crate中使用 (直接使用2个)
490KB
11K SLoC
Typify
Typify 将 JSON Schema 文档编译成 Rust 类型。它可以以多种方式使用
-
使用
cargo typify
命令 -
通过宏
import_types!("types.json")
直接在程序中生成 Rust 类型 -
通过构建器接口在
build.rs
或xtask
中生成 Rust 类型 -
通过构建器函数生成持久文件,例如在构建 API 绑定时
如果生成失败、无法编译或总体表现不佳:请提交问题,并包括 JSON Schema 和 Rust 输出(如果有)。使用 cargo typify
命令从命令行生成代码。如果您能明确表达您理想中的输出,那就更有帮助了。
JSON Schema → Rust 类型
Typify 根据模式的基本属性以几种不同的方式转换 JSON Schema 类型
内置类型
整数、浮点数、字符串等。这些在 Rust 中都有直接的表示。唯一需要注意的是如何根据类型属性选择适当的基本类型。例如,一个 JSON Schema 可能会指定一个最大值和/或最小值,这表明应使用哪种整数类型。
包含已知格式的字符串模式以适当的Rust类型表示。例如,{ "type": "string", "format": "uuid" }
表示为uuid::Uuid
(这需要将uuid
crate作为依赖项包含)。
数组
JSON模式数组可以转换为三种Rust类型Vec<T>
、HashSet<T>
和元组,具体取决于模式属性。数组可能具有与固定项目类型列表匹配的固定长度;这可以通过Rust元组很好地表示。当模式的uniqueItems
字段为false
或true
时,Vec<T>
和HashSet<T>
之间的区别。
对象
通常,对象转换为Rust结构体。但是,如果模式未定义属性,当additionalProperties
模式指定HashMap<String, T>
,否则为HashMap<String, serde_json::Value>
。
生成的struct
属性不在required
集合中时,通常表示为带有#[serde(default)]
属性的Option<T>
。具有已具有默认值(如Vec<T>
)的类型的不必要属性仅获得#[serde(default)]
属性。
OneOf
oneOf
结构映射到Rust枚举。Typify将其映射到各种serde枚举类型
。
AllOf
“allOf”结构通过合并模式处理。虽然大部分情况下,typify会尝试保留和共享类型名称,但在合并模式时,它并不总能做到这一点。您可能会遇到类型间重复的字段;优化这一生成过程是一个正在积极研究的工作领域。
AnyOf
anyOf
结构要复杂得多。它可能与enum
(oneOf
)非常接近,但没有任何特定的变体可能是规范或唯一的。尽管我们现在(不精确地)将这些作为具有可选、扁平成员的结构体来建模,但这代码生成的一个较弱领域。
欢迎并感谢描述示例模式和期望输出的反馈。
Rust -> Schema -> Rust
从Rust类型派生的模式可能包括一个扩展,该扩展提供有关原始类型的信息
{
"type": "object",
"properties": { .. },
"x-rust-type": {
"crate": "crate-o-types",
"version": "1.0.0",
"path": "crate_o_types::some_mod::SomeType"
}
}
该扩展包括crate的名称、Cargo风格的版本要求规范以及完整路径(必须以crate的ident-converted名称开头)。
使用typify的每种模式都允许指定一个crate及其版本列表。例如,如果用户指定“[email protected]”,则typify会使用其SomeType
类型,而不是根据模式生成一个。
使用其他crate中的类型
使用typify的每种模式都有一个方法来控制带有x-rust-type
注解的类型的使用。默认情况下是忽略它们。推荐的方法是指定您打算使用的每个crate及其版本。您还可以为crate提供*
版本(可能会导致不兼容)或定义一个策略,允许使用所有“未知”crate(可能需要为这些crate添加依赖项)。
对于CLI
$ cargo typify --unknown-crates allow --crate [email protected] ...
对于构建器
let mut settings = typify::TypeSpaceSettings::default();
settings.with_unknown_crates(typify::UnknownPolicy::Allow)
.with_crate("oxnet", typify::CrateVers::Version("1.0.0".parse().unwrap()));
对于宏
typify::import_types!(
schema = "schema.json",
unknown_types = Allow,
crates {
"oxnet" = "1.0.0"
}
)
版本要求
在x-rust-type
扩展中的version
字段遵循Cargo版本要求规范。如果扩展指定了crate的0.1.0
版本,并且用户声明他们正在使用0.1.1
版本,则使用该类型;反之,如果扩展指定了0.2.2
版本,并且用户只使用0.2.0
版本,则不使用该类型。
crate作者可以选择遵守比semver提供更大的稳定性。如果扩展版本是>=0.1.0, <1.00
,则crate作者承诺在所有版本直到1.0.0
之前,给定类型的模式兼容性。重要的是crate作者要填充version
字段,以维护类型的可用性。例如,虽然*
是一个有效的值,但它仅在有关类型的crate的第一个版本中可用,并且在任何后续版本中都不会不兼容地更改时才是有效的。
类型参数
x-rust-type
扩展也可以指定类型参数
{
"$defs": {
"Sprocket": {
"type": "object",
"properties": { .. },
"x-rust-type": {
"crate": "util",
"version": "0.1.0",
"path": "util::Sprocket",
"parameters": [
{
"$ref": "#/$defs/Gizmo"
}
]
}
},
"Gizmo": {
"type": "object",
"properties": { .. },
"x-rust-type": {
"crate": "util",
"version": "0.1.0",
"path": "util::Gizmo"
}
}
}
}
在类型生成期间指定的 util@0.1.0
crate,将使用(非生成的)类型 #/$defs/Sprocket
,该类型为 util::Sprocket<util::Gizmo>
。
parameters
字段是一个模式数组。它们可以是内联模式或引用模式。
在您的库中包含 x-rust-type
期望值的模式如下
{
"description": "schema for the x-rust-type extension",
"type": "object",
"properties": {
"crate": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+$"
},
"version": {
"description": "semver requirements per a Cargo.toml dependencies entry",
"type": "string"
},
"path": {
"type": "string",
"pattern": "^[a-zA-Z0-9_]+(::[a-zA-Z0-9+]+)*$"
},
"parameters": {
"type": "array",
"items": {
"$ref": "#/definitions/Schema"
}
}
},
"required": [
"crate",
"path",
"version"
]
}
version
字段表示您类型的稳定性。例如,如果 0.1.0
表示 0.1.1
用户可以使用该类型,而 0.2.0
用户将不会使用该类型(而是生成它)。您可以通过使用 Cargo 版本要求语法 来传达比 semver 所暗示的更远的未来承诺。例如,>=0.1.0, <1.0.0
指出,该类型将从 0.1.0
版本开始,直到 1.0.0
版本保持结构兼容。
格式化
您可以使用 rustfmt-wrapper 和 prettyplease 等crate来格式化生成的代码。这在使用代码或从 build.rs
发射代码时非常有用。
以下示例展示了将 TypeSpace
转换为字符串的不同方法(typespace
是 typify::TypeSpace
)。
rustfmt
最适合与手动编写的代码(如 xtask
或独立的代码生成器(如 cargo-typify
))一起检查的代码生成。
rustfmt_wrapper::rustfmt(typespace.to_stream().to_string())?
prettyplease
最适合 build.rs
脚本,因为传递依赖项可能未安装 rustfmt
,因此应该是自包含的。
prettyplease::unparse(&syn::parse2::<syn::File>(typespace.to_stream())?)
无格式化
如果没有人会看到代码(这几乎从不可能是这种情况)。
typespace.to_stream().to_string()
正在进行中
Typify 是一个正在进行中的项目。影响输出的更改将通过将 crate 版本号更新为破坏性更改来表示。
一般来说,如果您有一个导致 Typify 失败的 JSON Schema 或生成的类型不是您期望的类型,请提交一个问题。
有一些我们知道我们想改进的领域
复杂的 JSON Schema 类型
JSON模式可以表示多种类型。其中一些在Rust中易于建模,而另一些则不是。处理某些特殊类型的工作量很大。用户的示例在这方面非常有帮助。
有界数字
有界数字的处理并不太好。例如,考虑以下模式
{
"type": "integer",
"minimum": 1,
"maximum": 6
}
生成的类型不会强制执行这些值约束。
可配置的依赖项
将字符串模式中的format
设置为uuid
将导致uuid::Uuid
类型;同样,将format
设置为date
转换为chrono::naive::NaiveDate
。对于不想依赖uuid
或chrono
的用户,Typify可选择将这些表示为String
(或作为消费者指定的其他类型)。
依赖项
~3.5–5MB
~101K SLoC