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