| 0.3.5 |
|
|---|
1118 在 #serde 中排名
每月下载 36 次
在 80 个 crate(直接使用 3 个) 中使用
115KB
2.5K SLoC
serde-reflection
此 crate 提供了一种提取实现 Serde Serialize 和/或 Deserialize 特性的 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 用于提取具有“合理”实现的 Serde 特性 Serialize 和 Deserialize 的 Rust 容器(即结构和枚举)的格式。
支持的实施包括
-
使用
#[derive(Serialize, Deserialize)]获取的普通派生实现,用于 Serde 数据模型 中的 Rust 容器。 -
使用与二进制序列化格式兼容的 Serde 属性定制的派生实现,例如
#[serde(rename = ""Name"")]。 -
使用
Deserialize的手写实现,这些实现比派生实现更严格,前提是在跟踪过程中使用trace_value提供所有此类约束类型的样本值(请参阅下面的详细示例)。 -
只要每个枚举的第一个变体是递归无关的,就提供相互递归的类型。(例如,
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>)}。)
故障排除
此 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:: 追踪反序列化
反序列化追踪 API 接收一个类型 T、当前的追踪状态以及先前记录的样本引用作为输入。
核心算法和高级 API
核心算法 trace_type_once:: 通过探索 T 定义中出现的所有类型的图,尝试重建类型 T 的证值。同时,算法记录所有访问的结构体和枚举变体的格式。
为了使探索能够终止,核心算法 trace_type_once:: 仅探索每个可能的递归点一次(见下文段落)。特别是,如果 T 是枚举,则 trace_type_once:: 只在每次发现一个 T 的变体。
因此,高级 API trace_type:: 将重复调用 trace_type_once::,直到所有 T 的变体都已知。按照索引顺序探索 T 的变体情况,从索引 0 开始。
覆盖率保证
在以下假设下,单次调用 trace_type:: 保证记录 T 依赖的所有类型的格式。此外,如果 T 是枚举,它将记录 T 的所有变体。
(0) 容器名称不得冲突。如果发生这种情况,请考虑使用 #[serde(rename = "name")],或手动实现 serde 特性。
(1) 互相递归的枚举类型的第一种变体必须是“基本案例”。也就是说,对于每个枚举类型默认选择第一个变体(对于可选值使用None,对于序列使用[])必须保证类型声明图的深度优先遍历能够终止。
(2) 如果在反序列化过程中类型运行了自定义验证检查,必须之前通过调用trace_value提供样本值。此外,相应的已注册格式不应包含未知部分。
设计考虑
当我们使用反序列化回调遍历类型声明图时,类型系统要求我们返回有效 Rust 值类型V::Value,其中V是给定visitor的类型。这个约束限制了我们可以停止图遍历的情况,只有少数几种。
以下前4种情况是我们上面提到的可能的递归点
- 在第二次访问
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
~24K SLoC