Expand description
§proto_convert_derive
Derive seamless conversions between prost-generated Protobuf types and custom Rust types.
§Overview
proto_convert_derive is a procedural macro for automatically deriving
efficient, bidirectional conversions between Protobuf types generated by
prost and your native Rust structs.
This macro will significantly reduce boilerplate when you’re working with
Protobufs.
§Features
- Automatic Bidirectional Conversion: Derives
From<Proto>andInto<Proto>implementations. - Primitive Type Support: Direct mapping for Rust primitive types (
u32,i64,String, etc.). - Option and Collections: Supports optional fields (
Option<T>) and collections (Vec<T>). - Newtype Wrappers: Transparent conversions for single-field tuple structs.
- Field Renaming: Customize mapping between Rust and Protobuf field names using
#[proto(rename = "...")]. - Custom Conversion Functions: Handle complex scenarios with user-defined functions via
#[proto(derive_from_with = "...")]and#[proto(derive_into_with = "...")]. - Ignored Fields: Exclude fields from conversion using
#[proto(ignore)]. - Configurable Protobuf Module: Defaults to searching for types in a
protomodule, customizable per struct or globally.
§Usage
Given Protobuf definitions compiled with prost:
syntax = "proto3";
package service;
message Track {
uint64 track_id = 1;
}
message State {
repeated Track tracks = 1;
}Derive conversions in Rust:
ⓘ
use proto_convert_derive::ProtoConvert;
mod proto {
tonic::include_proto!("service");
}
#[derive(ProtoConvert)]
#[proto(module = "proto")]
pub struct Track {
#[proto(transparent, rename = "track_id")]
pub id: TrackId,
}
#[derive(ProtoConvert)]
pub struct TrackId(u64);
#[derive(ProtoConvert)]
pub struct State {
pub tracks: Vec<Track>,
}§Complex conversions, akin to serde(deserialize_with = “..”)
ⓘ
use std::collections::HashMap;
#[derive(ProtoConvert)]
#[proto(rename = "State")]
pub struct StateMap {
#[proto(derive_from_with = "into_map", derive_into_with = "from_map")]
pub tracks: HashMap<TrackId, Track>,
}
pub fn into_map(tracks: Vec<proto::Track>) -> HashMap<TrackId, Track> {
tracks.into_iter().map(|t| (TrackId(t.track_id), t.into())).collect()
}
pub fn from_map(tracks: HashMap<TrackId, Track>) -> Vec<proto::Track> {
tracks.into_values().map(Into::into).collect()
}§Ignoring fields:
ⓘ
use std::sync::atomic::AtomicU64;
#[derive(ProtoConvert)]
#[proto(rename = "State")]
pub struct ComplexState {
pub tracks: Vec<Track>,
#[proto(ignore)]
pub counter: AtomicU64,
}§Handle enums
enum Status {
STATUS_OK = 0;
STATUS_MOVED_PERMANENTLY = 1;
STATUS_FOUND = 2;
STATUS_NOT_FOUND = 3;
}
message StatusResponse {
Status status = 1;
string message = 2;
}
enum AnotherStatus {
OK = 0;
MOVED_PERMANENTLY = 1;
FOUND = 2;
NOT_FOUND = 3;
}ⓘ
// We do not require to use the STATUS prefix!
#[derive(ProtoConvert)]
pub enum Status {
Ok,
MovedPermanently,
Found,
NotFound,
}
#[derive(ProtoConvert)]
pub enum AnotherStatus {
Ok,
MovedPermanently,
Found,
NotFound,
}
#[derive(ProtoConvert)]
pub struct StatusResponse {
pub status: Status,
pub message: String,
}§Limitations
- Assumes Protobuf-generated types live in a single module.
- Optional Protobuf message fields (
optional) use.expectand panic if missing; handle accordingly.