11 个版本
0.4.0 | 2024 年 6 月 6 日 |
---|---|
0.3.6 | 2022 年 5 月 19 日 |
0.3.5 | 2021 年 10 月 12 日 |
0.3.3 | 2021 年 5 月 26 日 |
0.0.0 | 2020 年 3 月 25 日 |
#54 in 编码
88,038 每月下载次数
在 151 个 crate (21 directly) 中使用
120KB
2.5K SLoC
serde-reflection
此 crate 提供了一种提取实现 Serde Serialize 和/或 Deserialize 特性(s)的 Rust 容器的格式描述的方法。
格式描述在多个方面很有用
- 存储在版本控制下,可以测试格式以防止意外修改二进制序列化格式(例如,通过更改变体顺序)。
- 可以将格式传递给
serde-generate
以生成类定义并提供其他语言(C++、python、Java 等)中的 Serde 兼容的二进制序列化。
快速入门
通常,Serde 特性只是使用 Serde derive 宏实现。在这种情况下,您可以按以下方式获取格式描述
- 在所需的顶级容器定义上调用
trace_simple_type
,然后 - 为每个
enum
类型添加对trace_simple_type
的调用。 (这将修复任何MissingVariants
错误。)
#[derive(Deserialize)]
struct Foo {
bar: Bar,
choice: Choice,
}
#[derive(Deserialize)]
struct Bar(u64);
#[derive(Deserialize)]
enum Choice { A, B, C }
// Start the tracing session.
let mut tracer = Tracer::new(TracerConfig::default());
// Trace the desired top-level type(s).
tracer.trace_simple_type::<Foo>()?;
// Also trace each enum type separately to fix any `MissingVariants` error.
tracer.trace_simple_type::<Choice>()?;
// Obtain the registry of Serde formats and serialize it in YAML (for instance).
let registry = tracer.registry()?;
let data = serde_yaml::to_string(®istry).unwrap();
assert_eq!(&data, r#"---
Bar:
NEWTYPESTRUCT: U64
Choice:
ENUM:
0:
A: UNIT
1:
B: UNIT
2:
C: UNIT
Foo:
STRUCT:
- bar:
TYPENAME: Bar
- choice:
TYPENAME: Choice
"#);
功能和限制
serde_reflection
的目的是提取 Rust 容器(即结构和枚举)的格式,这些容器具有“合理”的 Serde 特性 Serialize
和 Deserialize
实现。
支持的功能
-
使用
#[derive(Serialize, Deserialize)]
在 Serde 数据模型 中为 Rust 容器获得的纯派生实现 -
使用与二进制序列化格式兼容的Serde属性进行自定义派生实现,例如:
#[serde(rename = "Name")]
。 -
在追踪期间使用
trace_value
提供所有此类约束类型的样本值,提供比派生实现更严格的Deserialize
的 手写实现。 -
如果每个枚举的第一个变体是递归无关的,则提供相互递归的类型。(例如,
enum List { None, Some(Box<List>)}
。)请注意,必须使用trace_type
单独追踪每个枚举以发现所有变体。
不受支持的习语
-
来自不同模块但具有相同基本名称(例如,
Foo
)的容器。(解决方法:使用#[serde(rename = ..)]
) -
在同一追踪会话中多次实例化的泛型类型。(解决方法:使用crate
serde-name
及其适配器SerializeNameAdapter
和DeserializeNameAdapter
。) -
与二进制格式不兼容的属性(例如,
#[serde(flatten)]
,#[serde(tag = ..)]
) -
追踪类型别名。(例如,
type Pair = (u32, u64)
将不会创建“Pair”条目。) -
相互递归的类型,选择每个枚举的第一个变体不会终止。(解决方案:重新排序变体。例如,以下代码
enum List { Some(Box<List>), None}
必须重写为enum List { None, Some(Box<List>)}
。) -
某些标准类型(例如
std::num::NonZeroU8
)可能不会被跟踪为容器,并在格式中仅以它们的底层原始类型(例如u8
)出现。这种信息丢失使得使用trace_value
来绕过反序列化不变性变得困难(见下例)。作为解决方案,您可以使用TracerConfig
覆盖原始类型的默认值(例如let config = TracerConfig::default().default_u8_value(1);
)。
安全注意事项
目前,Serde将HashSet<T>
和BTreeSet<T>
视为序列(即向量)。
使用BCS的加密应用程序必须使用HashMap<T, ()>
和BTreeMap<T, ()>
。使用HashSet<T>
或BTreeSet<T>
虽然可以编译,但BCS反序列化将不会强制执行规范性(意味着在这种情况下具有唯一、有序的序列化元素)。在HashSet<T>
的情况下,序列化将还会是非确定性的。
故障排除
该crate使用的错误类型提供了一个error.explanation()
方法,以帮助在格式跟踪期间进行故障排除。
详细示例
在以下更完整的示例中,我们提取了两个容器Name
和Person
的Serde格式,并演示了如何处理Name
的serde::Deserialize
的自定义实现。
use serde_reflection::{ContainerFormat, Error, Format, Samples, Tracer, TracerConfig};
#[derive(Serialize, PartialEq, Eq, Debug, Clone)]
struct Name(String);
// impl<'de> Deserialize<'de> for Name { ... }
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
enum Person {
NickName(Name),
FullName { first: Name, last: Name },
}
// Start a session to trace formats.
let mut tracer = Tracer::new(TracerConfig::default());
// Create a store to hold samples of Rust values.
let mut samples = Samples::new();
// For every type (here `Name`), if a user-defined implementation of `Deserialize` exists and
// is known to perform custom validation checks, use `trace_value` first so that `samples`
// contains a valid Rust value of this type.
let bob = Name("Bob".into());
tracer.trace_value(&mut samples, &bob)?;
assert!(samples.value("Name").is_some());
// Now, let's trace deserialization for the top-level type `Person`.
// We pass a reference to `samples` so that sampled values are used for custom types.
let (format, values) = tracer.trace_type::<Person>(&samples)?;
assert_eq!(format, Format::TypeName("Person".into()));
// As a byproduct, we have also obtained sample values of type `Person`.
// We can see that the user-provided value `bob` was used consistently to pass
// validation checks for `Name`.
assert_eq!(values[0], Person::NickName(bob.clone()));
assert_eq!(values[1], Person::FullName { first: bob.clone(), last: bob.clone() });
// We have no more top-level types to trace, so let's stop the tracing session and obtain
// a final registry of containers.
let registry = tracer.registry()?;
// We have successfully extracted a format description of all Serde containers under `Person`.
assert_eq!(
registry.get("Name").unwrap(),
&ContainerFormat::NewTypeStruct(Box::new(Format::Str)),
);
match registry.get("Person").unwrap() {
ContainerFormat::Enum(variants) => assert_eq!(variants.len(), 2),
_ => panic!(),
};
// Export the registry in YAML.
let data = serde_yaml::to_string(®istry).unwrap();
assert_eq!(&data, r#"---
Name:
NEWTYPESTRUCT: STR
Person:
ENUM:
0:
NickName:
NEWTYPE:
TYPENAME: Name
1:
FullName:
STRUCT:
- first:
TYPENAME: Name
- last:
TYPENAME: Name
"#);
使用trace_value
跟踪序列化
跟踪Rust值v
的序列化包括深度遍历v
的结构组件,并记录所有访问类型的Serde格式。
#[derive(Serialize)]
struct FullName<'a> {
first: &'a str,
middle: Option<&'a str>,
last: &'a str,
}
let mut tracer = Tracer::new(TracerConfig::default());
let mut samples = Samples::new();
tracer.trace_value(&mut samples, &FullName { first: "", middle: Some(""), last: "" })?;
let registry = tracer.registry()?;
match registry.get("FullName").unwrap() {
ContainerFormat::Struct(fields) => assert_eq!(fields.len(), 3),
_ => panic!(),
};
这种方法效果良好,但它只能恢复已经提供非平凡样本的数据类型的格式。
-
在枚举中,只有用户样本明确覆盖的变体将被记录。
-
在样本中提供
None
值或空向量[]
可能会导致部分未知格式的出现。
let mut tracer = Tracer::new(TracerConfig::default());
let mut samples = Samples::new();
tracer.trace_value(&mut samples, &FullName { first: "", middle: None, last: "" })?;
assert_eq!(tracer.registry().unwrap_err(), Error::UnknownFormatInContainer("FullName".to_string()));
因此,我们引入了一组补充API来跟踪类型的反序列化。
使用trace_type<T>
跟踪反序列化
反序列化跟踪API接受类型T
、当前的跟踪状态以及先前记录样本的引用作为输入。
核心算法和高级API
核心算法trace_type_once<T>
试图通过探索T
定义中出现的所有类型的图来重建类型T
的见证值。同时,算法记录了所有访问的结构体和枚举变体的格式。
为了使探索能够终止,核心算法trace_type_once<T>
只探索每个可能的递归点一次(见下文段落)。特别是,如果T
是枚举,trace_type_once<T>
一次只发现一个T
的变体。
因此,高级API trace_type<T>
将重复调用trace_type_once<T>
,直到所有T
的变体都已知。以索引0
开始的顺序探索T
的变体案例。
覆盖保证
在以下假设下,对trace_type<T>
的单次调用保证记录所有依赖于T
的类型格式。此外,如果T
是枚举,它将记录所有T
的变体。
(0) 容器名称不得冲突。如果发生这种情况,请考虑使用#[serde(rename = "name")]
,或手动实现serde特性。
(1) 相互递归枚举的第一个变体必须是“基本案例”。也就是说,为每个枚举类型默认为第一个变体(以及选项值的None
和序列的[]
)必须保证类型声明图的深度优先遍历的终止。
(2) 如果类型在反序列化期间执行自定义验证检查,则必须通过调用trace_value
之前提供样本值。此外,相应的注册格式不得包含未知部分。
设计考虑
每次我们在使用反序列化回调遍历类型声明的图时,类型系统要求我们返回有效的Rust值类型 V::Value
,其中 V
是给定 visitor
的类型。这个约束限制了我们可以停止图遍历的方式,仅限于几种情况。
前四种情况就是我们之前所说的 可能的递归点。
- 当第二次访问
Option<T>
时,我们选择返回值None
来停止; - 当第二次访问
Seq<T>
时,我们选择返回空序列[]
; - 当第二次访问
Map<K, V>
时,我们选择返回空映射{}
; - 当第二次访问
enum T
时,我们选择返回第一个变体,即通过假设(1)之上的“基本情况”。
除了上述情况之外,
- 当访问容器时,如果容器的名称映射到一个已记录的值,我们可以决定使用它。
默认配置 TracerConfig:default()
总是选择 NewTypeStruct
的记录值,在其它情况下从不这样做。
出于效率原因,当前算法不会尝试扫描除了主调用 trace_type<T>
的参数 T
之外的枚举变体。因此,每个枚举类型都必须单独跟踪。
贡献
有关如何帮助的说明,请参阅 CONTRIBUTING 文件。
许可证
本项目可根据 Apache 2.0 许可证 或 MIT 许可证 的条款使用。
依赖关系
~0.4–1MB
~23K SLoC