#serde #serialization #data-structures

已删除 serde-reflection-aptos

提取 Serde 数据格式的表示

0.3.5 2022年5月24日

1118#serde 中排名

每月下载 36
80 个 crate(直接使用 3 个) 中使用

MIT/Apache

115KB
2.5K SLoC

serde-reflection

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

此 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(&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 用于提取具有“合理”实现的 Serde 特性 SerializeDeserialize 的 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 及其适配器 SerializeNameAdapterDeserializeNameAdapter。)

  • 与二进制格式不兼容的属性(例如 #[serde(flatten)]#[serde(tag = ..)]

  • 跟踪类型别名。(例如,type Pair = (u32, u64)不会创建一个名为 "Pair" 的条目。)

  • 对于每个枚举的第一个变体选择不终止的相互递归类型。(解决方案:重新排序变体。例如,enum List { Some(Box<List>), None}必须重写为enum List { None, Some(Box<List>)}。)

故障排除

此 crate 使用的错误类型提供了一个方法 error.explanation(),以帮助在格式跟踪期间进行故障排除。

详细示例

在以下更完整的示例中,我们提取了两个容器 NamePerson 的 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(&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:: 追踪反序列化

反序列化追踪 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