# protobuf-convert Macros for convenient serialization of Rust data structures into/from Protocol Buffers. ## Introduction This is a fork of [exonum-derive](https://crates.io/crates/exonum-derive) with some changes to allow easier integration with other projects, and some new features. ## Usage First, add the dependency in `Cargo.toml`: ```toml protobuf-convert = "0.4.0" ``` Then, define a `ProtobufConvert` trait: ```rust trait ProtobufConvert { /// Type of the protobuf clone of Self type ProtoStruct; /// Struct -> ProtoStruct fn to_pb(&self) -> Self::ProtoStruct; /// ProtoStruct -> Struct fn from_pb(pb: Self::ProtoStruct) -> Result; } ``` And to use it, import the trait and the macro: For example, given the following protobuf: ```protobuf message Ping { fixed64 nonce = 1; } ``` rust-protobuf will generate the following struct: ```rust #[derive(PartialEq,Clone,Default)] pub struct Ping { // message fields pub nonce: u64, // special fields pub special_fields: ::protobuf::SpecialFields, } ``` We may want to convert that struct into a more idiomatic one, and derive more traits. This is the necessary code: ```rust // Import trait use crate::proto::ProtobufConvert; // Import macro use protobuf_convert::ProtobufConvert; // Import module autogenerated by protocol buffers use crate::proto::schema; #[derive(ProtobufConvert)] #[protobuf_convert(source = "schema::Ping")] struct Ping { nonce: u64, } ``` Note that the `ProtobufConvert` trait must be implemented for all the fields, see an example implementation for `u64`: ```rust impl ProtobufConvert for u64 { type ProtoStruct = u64; fn to_pb(&self) -> Self::ProtoStruct { *self } fn from_pb(pb: Self::ProtoStruct) -> Result { Ok(pb) } } ``` Now, converting between `Ping` and `schema::Ping` can be done effortlessly. ### `Enum` support A more complex example, featuring enums: ```protobuf message Ping { fixed64 nonce = 1; } message Pong { fixed64 nonce = 1; } message Message { oneof kind { Ping Ping = 1; Pong Pong = 2; } } ``` ```rust #[derive(ProtobufConvert)] #[protobuf_convert(source = "schema::Ping")] struct Ping { nonce: u64, } #[derive(ProtobufConvert)] #[protobuf_convert(source = "schema::Pong")] struct Pong { nonce: u64, } #[derive(ProtobufConvert)] #[protobuf_convert(source = "schema::Message")] enum Message { Ping(Ping), Pong(Pong), } ``` And it just works! You can also generate `From` and `TryFrom` traits for enum variants. Note that this will not work if enum has variants with the same field types. To use this feature add `impl_from_trait` attribute. ```rust #[derive(ProtobufConvert)] #[protobuf_convert(source = "schema::Message"), impl_from_trait] enum Message { Ping(Ping), Pong(Pong), } ``` `From`, `From` and also `TryFrom<..>` traits will be generated. Another attribute that can be used with enum is `rename`. It instructs macro to generate methods with case specified in attribute param. ```rust #[derive(ProtobufConvert)] #[protobuf_convert(source = "schema::Message"), rename(case = "snake_case")] enum Message { Ping(Ping), Pong(Pong), } ``` Currently, only snake case is supported. ### Skipping fields This macro also supports skipping fields in `struct`s so they are ignored when serializing, i.e they will not be mapped to any field in the schema: ```rust #[derive(ProtobufConvert)] #[protobuf_convert(source = "schema::Ping")] struct Ping { pub nonce: u64, #[protobuf_convert(skip)] my_private_field: u64 } ``` Note that you can only skip fields whose type implements the `Default` trait. ### Overriding conversion rules This macro also supports serde-like attribute `with` for modules with the custom implementation of `from_pb` and `to_pb` conversions. `protobuf-convert` will use functions `$module::from_pb` and `$module::to_pb` instead of `ProtobufConvert` trait for the specified field. ```rust #[derive(Debug, Clone, Copy, Eq, PartialEq)] enum CustomId { First = 5, Second = 15, Third = 35, } #[derive(Debug, Clone, ProtobufConvert, Eq, PartialEq)] #[protobuf_convert(source = "proto::SimpleMessage")] struct CustomMessage { #[protobuf_convert(with = "custom_id_pb_convert")] id: Option, name: String, } mod custom_id_pb_convert { use super::*; pub(super) fn from_pb(pb: u32) -> Result, anyhow::Error> { match pb { 0 => Ok(None), 5 => Ok(Some(CustomId::First)), 15 => Ok(Some(CustomId::Second)), 35 => Ok(Some(CustomId::Third)), other => Err(anyhow::anyhow!("Unknown enum discriminant: {}", other)), } } pub(super) fn to_pb(v: &Option) -> u32 { match v { Some(id) => *id as u32, None => 0, } } } ``` ## See also * [rust-protobuf](https://github.com/stepancheg/rust-protobuf)