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

以下是完整的示例代码:

  1. 首先安装protoc-gen-prost-serde:
cargo install protoc-gen-prost-serde
  1. 创建proto文件greeter.proto:
syntax = "proto3";

package greeter.v1;

message Greeting {
  string name = 1;
  int32 age = 2;
}

service Greeter {
  rpc Greet (Greeting) returns (Greeting);
}
  1. 使用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
  1. 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);
}
  1. Cargo.toml依赖:
[dependencies]
prost = "0.11"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

这个示例展示了如何利用protoc-gen-prost-serde实现Protobuf消息与JSON的相互转换。


1 回复

Rust Protobuf序列化插件protoc-gen-prost-serde的使用:结合Prost和Serde实现高效协议缓冲数据编解码

介绍

protoc-gen-prost-serde是一个Protocol Buffers编译器插件,它结合了Prost和Serde两个强大的Rust库,为Protobuf消息生成带有Serde支持的Rust代码。这个插件允许你在保持高性能Protobuf序列化的同时,还能获得Serde提供的灵活序列化能力(如JSON、YAML等格式)。

主要特点

  1. 高性能:基于Prost的高效Protobuf实现
  2. 灵活性:通过Serde支持多种序列化格式
  3. 无缝集成:自动为生成的Protobuf消息实现Serde的Serialize和Deserialize trait
  4. 代码生成:作为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\")]");

性能建议

  1. 对于纯Protobuf操作,直接使用Prost的编解码方法性能最佳
  2. 当需要与其他系统交换数据时,再使用Serde的序列化
  3. 考虑使用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

  1. 项目结构:
protobuf-demo/
├── Cargo.toml
├── build.rs
├── src/
│   └── main.rs
└── proto/
    └── message.proto
  1. 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"
  1. build.rs:
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();
}
  1. 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;
  }
}
  1. 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属性的使用
回到顶部