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 编码

Download history 29882/week @ 2024-05-02 29920/week @ 2024-05-09 44475/week @ 2024-05-16 40496/week @ 2024-05-23 42316/week @ 2024-05-30 44939/week @ 2024-06-06 48153/week @ 2024-06-13 43146/week @ 2024-06-20 42566/week @ 2024-06-27 22820/week @ 2024-07-04 23074/week @ 2024-07-11 20471/week @ 2024-07-18 21278/week @ 2024-07-25 17391/week @ 2024-08-01 22514/week @ 2024-08-08 22389/week @ 2024-08-15

88,038 每月下载次数
151 个 crate (21 directly) 中使用

MIT/Apache

120KB
2.5K SLoC

serde-reflection

serde-reflection on crates.io Documentation (latest release) License License

此 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(&registry).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 特性 SerializeDeserialize 实现。

支持的功能

  • 使用 #[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及其适配器SerializeNameAdapterDeserializeNameAdapter。)

  • 与二进制格式不兼容的属性(例如,#[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()方法,以帮助在格式跟踪期间进行故障排除。

详细示例

在以下更完整的示例中,我们提取了两个容器NamePerson的Serde格式,并演示了如何处理Nameserde::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(&registry).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