#protobuf #macro #prost

proto-mapper

一个宏库,用于更轻松地在自定义模型和由proto生成的代码之间进行映射

3个版本

0.1.2 2023年11月1日
0.1.1 2023年10月31日
0.1.0 2023年10月31日

#479 in Rust模式

MIT 许可证

16KB
118

Proto Mapper

github CI/main crates.io

自定义模型和protobuf生成的代码之间的宏实现库

注意

这个库是对protobuf-convert库的(几乎)完全重写。重写目的是为了适应我们项目的特定需求。主要概念和思想保持不变,因此,荣誉归功于protobuf-convert库的原始作者。

变化了什么

这个库

  • 将宏的主要名称更改为ProtoMap
  • 改变了宏的使用方式和与外部特质的接口
  • 避免在客户端模块中重新实现ProtoMap特质
  • 重构为不同的crate
  • 包含对边缘情况的过度测试
  • 引入ProtoScalar类型
  • 引入用于protobuf标量类型的ProtoScalarMap特质
  • 自动处理枚举protobuf生成代码
  • 通过扫描应用结构的类型来处理选项值,并选择不同的实现路径
  • 支持prost

安装

首先,在Cargo.toml中添加依赖项

proto-mapper = {version = "0.1.2", features = ["protobuf"] } 

或者

proto-mapper = {version = "0.1.2", features = ["prost"] } 

注意:功能prostprotobuf相互排斥且必需的。根据您使用的目标生成的代码proto框架,使用其中一个

用法

可以在这里找到展示该库使用的概念证明。请注意,PoC仍在进行中。

映射标量值和枚举

给定protobuf枚举和消息

syntax = "proto3";

enum EntityStatus {
  STATUS_A = 0;
  STATUS_B = 1;
  STATUS_C = 2;
}

message ScalarEntity {
  uint32 uint32_f = 1;
  int32 int32_f= 2;
  bool bool_f = 4;
  string string_f = 5;
  int64  int64_f = 6;
  uint64 uint64_f  = 7;
  bytes bytes_f = 8;
  float float_f = 9;
  double double_f = 10;

  EntityStatus status = 11;
}

使用prostrust-protobuf库生成代码后,您可以将您的自定义模型映射到生成的结构体,如下所示


#[derive(Debug, Clone, Copy, Default, PartialEq, ProtoMap)]
#[proto_map(
source = "proto::EntityStatus",
enumeration,
)]
enum EntityStatus {
    #[default]
    StatusA,
    StatusB,
    StatusC,
}

#[derive(Debug, ProtoMap, Default)]
#[proto_map(source = "proto::ScalarEntity")]
struct ScalarEntity {
    pub uint32_f: u32,
    pub int32_f: i32,
    pub bool_f: bool,
    pub string_f: String,
    pub bytes_f: Vec<u8>,
    pub int64_f: i64,
    pub uint64_f: u64,
    pub float_f: f32,
    pub double_f: f64,
    #[proto_map(enumeration)]
    pub status: EntityStatus,
}

然后您可以将您定义的结构体和生成的代码之间进行转换,如下所示

let e = ScalarEntity::default();
let p = e.to_proto();

您还可以将proto实例转换为您的自定义结构体。

let p = proto::ScalarEntity::default();
let e = ScalarEntity::from_proto(p)?;

请注意,枚举的映射代码需要在Rust枚举上使用#[proto_map(..., enumeration)]属性,还需要在ScalarEntity内部的字段上进行标记。

映射可选标量值和枚举

给定相同的 proto 文件。开箱即用即可映射到可选值

也就是说

#[derive(Debug, ProtoMap, PartialEq, Default)]
#[proto_map(source = "proto::prost::ScalarEntity")]
struct ScalarEntityOptions {
    pub uint32_f: Option<u32>,
    pub int32_f: Option<i32>,
    pub bool_f: Option<bool>,
    pub string_f: Option<String>,
    pub bytes_f: Option<Vec<u8>>,
    pub int64_f: Option<i64>,
    pub uint64_f: Option<u64>,
    pub float_f: Option<f32>,
    pub double_f: Option<f64>,
    #[proto_map(enumeration)]
    pub status: Option<EntityStatus>,
}

宏扫描自定义结构体中注解的类型,并选择不同的实现路径以进行转换代码。

映射非标量值

给定 proto 文件

syntax = "proto3";

// ... definitions of ScalarEntity

message NestedEntity {
  ScalarEntity first = 1;
  ScalarEntity second = 2;
}

您可以如下映射非标量值

#[derive(Debug, ProtoMap, PartialEq)]
#[proto_map(source = "proto::NestedEntity")]
struct NestedEntity {
    pub first: ScalarEntity,
    pub second: Option<ScalarEntity>,
}

将非标量 oneof 字段映射到 Rust 枚举

您可以将顶级 oneof protobuf 字段映射如下

给定 proto 文件

syntax = "proto3";

// ... definitions of ScalarEntity

message HierarchyEntity {
  oneof data {
    ScalarEntity first_entity = 1;
    NestedEntity second_entity = 2;
  }
}

然后自定义结构体的一个实现可能如下

#[derive(Debug, ProtoMap, PartialEq)]
#[proto_map(
    source = "proto::HierarchyEntity",
    one_of(field = "data"),
    rename_variants = "snake_case"
)]
enum HierarchyEntity {
    FirstEntity(ScalarEntity),
    SecondEntity(NestedEntity),
}

请注意,rename_variants 属性可以取两个值 snake_caseSTREAMING_SNAKE_CASE,具体取决于目标生成的结构体。

自定义映射标量值

请参阅测试示例 针对 prost针对 rust-protobuf

prostrust-protobuf 使用之间的差异

待办事项

它的工作原理

内部,宏在标量和非标量类型之间进行区分。

以下列出标量类型,以及 rust-protobufprost 自动生成的代码映射的 protobuf 类型。

Protobuf 类型 Rust 类型
double f64
float f32
int32 i32
int64 i64
uint32 u32
uint64 u64
sint32 i32
sint64 i64
fixed32 u32
fixed64 u64
sfixed32 i32
sfixed64 i64
bool bool
string String
bytes Vec<u8>
(表来自 prost 项目 README.md)

所有其他 Rust 类型都视为非标量。但有一个例外是 protobuf enum 类型,需要使用元属性 #[proto_map(enumeration)] 标记。

库根据结构体类型自动实现两个特质。

对于标量类型

pub trait ProtoMapScalar<P: ProtoScalar>: Sized {
    /// Converts a reference of [`Self`] to a [`ProtoScalar`]
    fn to_scalar(&self) -> P;

    /// Consumes a [`ProtoScalar`] and returns a [`Self`] or error in the conversion failed
    fn from_scalar(proto: P) -> Result<Self, anyhow::Error>;
}

对于非标量类型

pub trait ProtoMap
    where
        Self: Sized,
{
    type ProtoStruct;
    /// Converts a reference of [`Self`] struct to proto [`Self::ProtoStruct`]
    fn to_proto(&self) -> Self::ProtoStruct;

    /// Consumes a proto [`Self::ProtoStruct`] and returns a [`Self`] struct or error in the conversion failed
    fn from_proto(proto: Self::ProtoStruct) -> Result<Self, anyhow::Error>;
}

请注意,enum 类型在 rust-protobuf 中被视为非标量,但在 prost 中被视为标量(i32 值)。

库还为所有 proto 标量类型提供了一个名为 ProtoScalar 的特质实现。

要了解宏的实现,请参阅 prost 手动测试rust-protobuf 手动测试,这些测试用作创建实现的指南。

局限性

待办事项

资源

依赖关系

~0.8–3.5MB
~56K SLoC