4 个版本 (1 个稳定版)
1.0.0 | 2024年2月26日 |
---|---|
0.3.0 | 2024年2月16日 |
0.2.0 | 2024年2月8日 |
0.1.0 | 2024年2月5日 |
#219 在 Rust 模式
35KB
584 行
为什么选择类型?
使用 类型,您
- 在 Rust 中使用类型标识符,且无需 Uuid 的开销
- 使类型在 JSON 和任何导出中可读且明显
- (如果启用 sqlx 功能)仍然可以在数据库中使用 uuid
- 无需编写代码,无需显式字符串化、解析、检查类型等。
- 无需声明类型和标识符的样板代码
- 您的 id 实现了 Copy、Display、FromStr、Serialize、Deserialize、Eq、Hash 等功能
- 安全地处理同一类型的已标识对象和新的对象
另请参阅 类型动机和操作介绍。
可选功能
- serde: 为
Id
、Ided
以及id_enum!
枚举实现Serialize
和Deserialize
实现 - sqlx: 对于
Id
(具有 uuid 列)和Ided
(具有 uuid 标识符的表)实现透明的读写 - jsonschema: JSON 架构生成
- openapi: 为
Id
实现开放 API ID 对象类型
在当前版本中,sqlx 功能仅对 postgresql 完整支持。
声明对象类型
您可以实现 Identifiable
特性,但最简单的解决方案是直接在您的结构体中添加属性
use kind::*;
#[derive(Identifiable)]
#[kind(class="Cust")]
pub struct Customer {
// many fields
}
#[derive(Identifiable)]
#[kind(class="Cont")]
pub struct Contract {
// many fields
}
Id
kind::Id
是强类型化的,以避免滥用 Rust API,尤其是在函数需要多个不同类型的 id 时。
kind::Id
还通过在内部使用的 id 前缀一个类前缀来防止任何基于字符串的 API(如 Rest 或 GraphQL)的滥用。
无成本:类型由类型系统处理,不会使编译的二进制文件变得混乱
assert_eq!(
std::mem::size_of::<Id<Customer>>(),
std::mem::size_of::<uuid::Uuid>(),
);
您可以从JSON或其他字符串中解析ID。
let id: Id<Customer> = "Cust_371c35ec-34d9-4315-ab31-7ea8889a419a"
.parse().unwrap();
类型已检查,因此此客户ID不能误用为合同ID。
assert!(
"Cust_371c35ec-34d9-4315-ab31-7ea8889a419a"
.parse::<Id<Contract>>()
.is_err()
);
注意:公共ID以不区分大小写的方式解析和检查。
assert_eq!(id, "cust_371c35ec-34d9-4315-ab31-7ea8889a419a".parse());
assert_eq!(id, "CUST_371C35EC-34D9-4315-AB31-7EA8889A419A".parse());
已标记
Ided
是“已识别”的缩写。
有时,您必须处理没有ID的原始对象,因为这正是您从REST API创建时收到的,或者因为您在将行插入数据库时才提供ID。
这就是为什么我们的原始Customer
类型没有ID。大多数API不处理仅仅是原始的Customer
类型,而是处理一个Ided<Customer>
,它保证有一个ID。
可以从ID和“实体”创建一个已标记的对象。
let new_customer = Customer { name: "John".to_string() };
let customer = Ided::new(id, new_customer);
assert_eq!(
customer.id().to_string(),
"Cust_371c35ec-34d9-4315-ab31-7ea8889a419a"
);
assert_eq!(customer.entity().name, "John");
已标记对象自动解析为实体类型,因此这也有效。
assert_eq!(customer.name, "John");
序列化
Ided
对象以ID与其他字段相邻的方式序列化,没有不必要的嵌套。
#[derive(Identifiable, serde::Serialize, serde::Deserialize)]
#[kind(class="Cust")]
pub struct Customer {
pub name: String,
}
let json = r#"{
"id": "Cust_371c35ec-34d9-4315-ab31-7ea8889a419a",
"name": "John"
}"#;
let customer: Ided<Customer> = serde_json::from_str(&json).unwrap();
assert_eq!(
customer.id().to_string(),
"Cust_371c35ec-34d9-4315-ab31-7ea8889a419a"
);
assert_eq!(customer.name, "John");
检查ID类型,以下反序列化失败,因为ID的前缀错误。
let json = r#"{
"id": "Con_371c35ec-34d9-4315-ab31-7ea8889a419a",
"name": "John"
}"#;
assert!(serde_json::from_str::<Ided<Customer>>(&json).is_err());
sqlx/PostgreSQL
在数据库中,ID只是一个uuid
。数据库中ID的类型由查询和您的数据库结构隐式给出,读取/写入Rust到数据库时没有额外的检查,您在开始使用Kind时不需要更改数据库结构。
Id
类型实现了Encode
和Decode
,因此它可以像任何其他主类型一样在sqlx查询中透明地使用(也支持Id
的数组,这在PostgreSQL中很方便)。
至于serde,FromRow
在Ided
上的实现是从原始结构上的实现自动推导出来的。
因此,您通常会像这样声明结构体,以便从包含ID列和原始结构体列的sqlx::Row
加载Ided
。
#[derive(Identifiable, sqlx::FromRow)]
#[kind(class="Cust")]
pub struct Customer {
pub name: String,
}
JSON模式
如果您正在使用schemars crate为您对象生成JSON模式,您可以启用jsonschema
功能,我们将为Id
对象和任何Ided
对象生成定义。
#[derive(JsonSchema, Identifiable)]
#[kind(class="Cust")]
pub struct Customer {
pub name: String
}
fn main() {
let schema = schema_for!(Ided<Customer>);
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
}
将打印出
{
"$schema": "https://json-schema.fullstack.org.cn/draft-07/schema#",
"title": "Customer_ided",
"description": "Identified version of Customer",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "string"
},
"name": {
"type": "string"
}
}
}
Open API
Open API支持(由openapi
功能标志控制)目前非常基础。到目前为止,唯一支持的功能是定义可由其他模式引用的schema级别的Id
对象。
将Id
包含到生成的模式中的示例
pub struct ApiDoc;
impl utoipa::OpenApi for ApiDoc {
fn openapi() -> utoipa::openapi::OpenApi {
let mut components = utoipa::openapi::ComponentsBuilder::new();
let (kind_name, kind_schema) = kind::openapi_schema();
components = components.schema(kind_name, kind_schema);
//extra components and paths
let mut openapi = utoipa::openapi::OpenApiBuilder::new()
.components(Some(components.build()))
.build();
openapi
}
}
依赖关系
~2-17MB
~245K SLoC