Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/witnet/protobuf-convert

Macros for convenient serialization of Rust data structures into/from Protocol Buffers 3
https://github.com/witnet/protobuf-convert

buffers macro protobuf protocol rust schema serialization

Last synced: about 21 hours ago
JSON representation

Macros for convenient serialization of Rust data structures into/from Protocol Buffers 3

Awesome Lists containing this project

README

        

# 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)]
#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
pub struct Ping {
// message fields
pub nonce: u64,
// special fields
#[cfg_attr(feature = "with-serde", serde(skip))]
pub unknown_fields: ::protobuf::UnknownFields,
#[cfg_attr(feature = "with-serde", serde(skip))]
pub cached_size: ::protobuf::CachedSize,
}
```

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)