Rust Protobuf序列化插件protoc-gen-prost-serde的使用:结合Prost和Serde实现高效协议缓冲数据编解码
Rust Protobuf序列化插件protoc-gen-prost-serde的使用:结合Prost和Serde实现高效协议缓冲数据编解码
protoc-gen-prost-serde介绍
protoc-gen-prost-serde是一个protoc插件,它生成遵循protobuf-JSON约定的serde序列化实现。
在仅使用Rust代码的项目中,使用Prost生成protobuf定义的首选方法是使用prost-build通过build.rs文件,然后使用pbjson-build生成serde实现。然而,在多语言环境中工作时,利用Protocol Buffers生态系统中的通用工具可能会更有利。一个常用的工具是buf,它简化了代码生成过程并包含几个有用的功能,包括linting、包管理和破坏性变更检测。
使用方法
确保protoc-gen-prost-serde已安装在$PATH目录中。然后从命令行如下调用protoc:
protoc --prost-serde_out=proto/gen -I proto proto/greeter/v1/greeter.proto
选项
该工具支持来自pbjson-build的所有相同选项。有关这些设置效果的更多信息,请参阅该crate的相关文档:
- btree_map=<proto_path>
- default_package_filename=<value>
- extern_path=<proto_path>=<rust_path>
- retain_enum_prefix(=true)
- preserve_proto_field_names(=true)
- ignore_unknown_fields(=true)
- emit_fields(=true)
- use_integers_for_enums(=true)
此外,还可以指定以下选项:
- no_include(=true):跳过添加到由protoc-gen-prost生成的文件中的包含。如果此插件在单独的protoc调用中运行并且遇到Tried to insert into file that doesn’t exist错误,则可能需要此行为。
关于参数值的说明:
- <proto_path>:以.开始的Protobuf路径将从全局根(前缀匹配)匹配。所有其他路径将作为后缀匹配。
- (=true):布尔值可以在参数后指定,但如果没有,则假定值为true。
与protoc和protoc-gen-prost一起使用
为了使与prost生成的基础定义一起工作更容易,此插件假设它与protoc-gen-prost在同一protoc调用中以链式模式运行。可以通过在同一protoc调用中指定多个插件来完成,如下所示:
protoc -I proto proto/greeter/v1/greeter.proto \
--prost_out=proto/gen \
--prost_opt=compile_well_known_types \
--prost_opt=extern_path=.google.protobuf=::pbjson_types \
--prost-serde_out=proto/gen
当作为单独的调用运行时,protoc不会知道由protoc-gen-prost生成的基础定义。在这种情况下,需要使用no_include指令,并且需要单独包含生成的.serde.rs文件。
protoc -I proto proto/greeter/v1/greeter.proto \
--prost_out=proto/gen \
--prost_opt=compile_well_known_types \
--prost_opt=extern_path=.google.protobuf=::pbjson_types
protoc -I proto proto/greeter/v1/greeter.proto \
--prost-serde_out=proto/gen \
--prost-serde_opt=no_include
与buf一起使用
当与buf一起使用时,可以在buf.gen.yaml文件中指定选项。protoc-gen-prost-serde应作为插件步骤出现在任何protoc-gen-prost步骤之后。此外,应指定compile_well_known_types和extern_path=.google.protobuf=::pbjson_types选项。
version: v1
plugins:
- plugin: prost
out: gen
opt:
- compile_well_known_types
- extern_path=.google.protobuf=::pbjson_types
- plugin: prost-serde
out: gen
protoc-gen-prost-serde插件可以与protoc-gen-prost-crate插件兼容:
version: v1
plugins:
- plugin: prost
out: gen
opt:
- compile_well_known_types
- extern_path=.google.protobuf=::pbjson_types
- plugin: prost-serde
out: gen
- plugin: prost-crate
strategy: all
out: gen
opt:
- no_features
完整示例demo
以下是完整的示例代码:
- 首先安装protoc-gen-prost-serde:
cargo install protoc-gen-prost-serde
- 创建proto文件greeter.proto:
syntax = "proto3";
package greeter.v1;
message Greeting {
string name = 1;
int32 age = 2;
}
service Greeter {
rpc Greet (Greeting) returns (Greeting);
}
- 使用protoc生成Rust代码:
protoc -I proto proto/greeter/v1/greeter.proto \
--prost_out=proto/gen \
--prost_opt=compile_well_known_types \
--prost_opt=extern_path=.google.protobuf=::pbjson_types \
--prost-serde_out=proto/gen
- Rust项目中使用生成的代码:
use greeter::v1::Greeting;
use serde_json;
fn main() {
// 创建Greeting消息
let greeting = Greeting {
name: "Alice".to_string(),
age: 30,
};
// 序列化为JSON
let json = serde_json::to_string(&greeting).unwrap();
println!("Serialized: {}", json);
// 从JSON反序列化
let deserialized: Greeting = serde_json::from_str(&json).unwrap();
println!("Deserialized: name={}, age={}", deserialized.name, deserialized.age);
}
- Cargo.toml依赖:
[dependencies]
prost = "0.11"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
这个示例展示了如何利用protoc-gen-prost-serde实现Protobuf消息与JSON的相互转换。
Rust Protobuf序列化插件protoc-gen-prost-serde的使用:结合Prost和Serde实现高效协议缓冲数据编解码
介绍
protoc-gen-prost-serde
是一个Protocol Buffers编译器插件,它结合了Prost和Serde两个强大的Rust库,为Protobuf消息生成带有Serde支持的Rust代码。这个插件允许你在保持高性能Protobuf序列化的同时,还能获得Serde提供的灵活序列化能力(如JSON、YAML等格式)。
主要特点
- 高性能:基于Prost的高效Protobuf实现
- 灵活性:通过Serde支持多种序列化格式
- 无缝集成:自动为生成的Protobuf消息实现Serde的Serialize和Deserialize trait
- 代码生成:作为protoc插件直接集成到构建流程中
安装方法
首先需要安装必要的工具链:
# 安装protoc编译器
# 安装protoc-gen-prost-serde插件
cargo install protoc-gen-prost-serde
# 安装prost相关依赖
cargo add prost prost-types
cargo add serde --features derive
使用方法
1. 定义Protobuf消息
创建message.proto
文件:
syntax = "proto3";
package tutorial;
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
2. 配置build.rs
在项目的build.rs
文件中添加构建配置:
fn main() -> Result<(), Box<dyn std::error::Error>> {
std::env::set_var("PROTOC", protoc_bin_vendored::protoc_bin_path().unwrap());
tonic_build::configure()
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
.compile(&["message.proto"], &["."])?;
Ok(())
}
或者使用prost-build:
fn main() {
let mut config = prost_build::Config::new();
config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
config
.compile_protos(&["message.proto"], &["."])
.unwrap();
}
3. 使用生成的代码
mod tutorial {
include!(concat!(env!("OUT_DIR"), "/tutorial.rs"));
}
use tutorial::Person;
fn main() {
// 创建Person对象
let mut person = Person {
name: "John Doe".to_string(),
id: 1234,
email: "johndoe@example.com".to_string(),
};
// Protobuf序列化
let protobuf_bytes = prost::Message::encode_to_vec(&person).unwrap();
// Protobuf反序列化
let decoded_person = Person::decode(&protobuf_bytes[..]).unwrap();
// 使用Serde序列化为JSON
let json_str = serde_json::to_string(&person).unwrap();
println!("JSON: {}", json_str);
// 从JSON反序列化
let from_json: Person = serde_json::from_str(&json_str).unwrap();
}
高级配置
自定义字段属性
在.proto
文件中可以使用注解自定义Serde属性:
message Person {
string name = 1 [(prost.serde_attributes) = { rename = "full_name" }];
int32 id = 2 [(prost.serde_attributes) = { default = 0 }];
string email = 3 [(prost.serde_attributes) = { skip_serializing_if = "is_empty" }];
}
使用不同的Serde特性
在build.rs
中可以为特定消息或字段添加不同的Serde特性:
config.type_attribute("tutorial.Person", "#[derive(serde::Serialize, serde::Deserialize)]");
config.field_attribute("tutorial.Person.name", "#[serde(rename = \"full_name\")]");
性能建议
- 对于纯Protobuf操作,直接使用Prost的编解码方法性能最佳
- 当需要与其他系统交换数据时,再使用Serde的序列化
- 考虑使用
prost::Message::encoded_len()
预先计算缓冲区大小以减少分配
常见问题解决
问题1:编译时出现"protoc not found"错误
- 解决方案:确保protoc编译器已安装并在PATH中,或通过
PROTOC
环境变量指定路径
问题2:Serde序列化时遇到未实现错误
- 解决方案:确保在build.rs中正确配置了type_attribute,并且项目依赖了serde的derive特性
问题3:如何处理Protobuf的oneof字段
- 解决方案:oneof字段会自动生成枚举,可以通过
#[serde(tag = "type", content = "content")]
等属性自定义序列化方式
完整示例代码
以下是一个完整的项目示例,展示如何使用protoc-gen-prost-serde
:
- 项目结构:
protobuf-demo/
├── Cargo.toml
├── build.rs
├── src/
│ └── main.rs
└── proto/
└── message.proto
- Cargo.toml:
[package]
name = "protobuf-demo"
version = "0.1.0"
edition = "2021"
[dependencies]
prost = "0.11"
prost-types = "0.11"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[build-dependencies]
prost-build = "0.11"
protoc-bin-vendored = "3.0"
fn main() {
let mut config = prost_build::Config::new();
// 为所有生成的类型添加Serde支持
config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
// 自定义特定字段的Serde属性
config.field_attribute("tutorial.Person.name", "#[serde(rename = \"full_name\")]");
// 编译proto文件
std::env::set_var("PROTOC", protoc_bin_vendored::protoc_bin_path().unwrap());
config
.compile_protos(&["proto/message.proto"], &["."])
.unwrap();
}
- proto/message.proto:
syntax = "proto3";
package tutorial;
message Person {
string name = 1;
int32 id = 2;
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
oneof test_oneof {
string test_string = 5;
int32 test_int = 6;
}
}
- src/main.rs:
mod tutorial {
include!(concat!(env!("OUT_DIR"), "/tutorial.rs"));
}
use tutorial::{Person, person::{PhoneNumber, PhoneType}};
use serde_json;
fn main() {
// 创建Person对象
let person = Person {
name: "Alice".into(),
id: 1001,
email: "alice@example.com".into(),
phones: vec![
PhoneNumber {
number: "123-4567".into(),
type_: PhoneType::Home.into(),
}
],
test_oneof: Some(tutorial::person::TestOneof::TestInt(42)),
};
// 1. Protobuf二进制序列化
let protobuf_data = prost::Message::encode_to_vec(&person).unwrap();
println!("Protobuf序列化大小: {} bytes", protobuf_data.len());
// 2. Protobuf反序列化
let decoded_person = Person::decode(&protobuf_data[..]).unwrap();
println!("反序列化后姓名: {}", decoded_person.name);
// 3. JSON序列化 (使用Serde)
let json_str = serde_json::to_string_pretty(&person).unwrap();
println!("JSON格式:\n{}", json_str);
// 4. 从JSON反序列化
let from_json: Person = serde_json::from_str(&json_str).unwrap();
println!("从JSON反序列化的ID: {}", from_json.id);
// 5. 处理oneof字段
if let Some(tutorial::person::TestOneof::TestInt(i)) = from_json.test_oneof {
println!("Oneof字段的值: {}", i);
}
}
这个完整示例展示了:
- Protobuf消息定义
- build.rs配置
- Protobuf二进制序列化/反序列化
- JSON格式转换
- 复杂类型(如嵌套消息、枚举、oneof字段)的处理
- 自定义Serde属性的使用