brotopuf
brotopuf is a library for serializing and deserializing structs using the
protocol buffer wire format.
It does not implement all of the features of protocol buffers. In particular,
there is no compiler, and required fields and oneof restrictions are not enforced.
Usage
To make a struct serializable/deserializable, annotate it with the Serialize and/or
Deserialize macros, and tag the fields with numbers using the id attribute.
use brotopuf::Deserialize;
use brotopuf::DeserializeField;
use brotopuf::Serialize;
#[derive(Serialize, Deserialize)]
struct TestMessage {
#[id(1)]
int32: i32,
#[id(2)]
int64: i64,
}
Classes marked with Serialize implement the Serialize trait, and have the serialize method.
pub trait Serialize {
fn serialize_field(&self, id: u64, pbtype: ProtoType, w: &mut impl Write) -> io::Result<()>;
fn serialize(&self, w: &mut impl Write) -> io::Result<()>;
}
let mut v: Vec<u8> = Vec::new();
s.serialize(&mut v).unwrap();
Likewise for Deserialize:
pub trait Deserialize {
fn deserialize(&mut self, r: &mut impl Read) -> Result<(), DeserializeError>;
}
let v: Vec<u8> = vec![
0x08, 0x96, 0x01, ];
let mut msg = TestMessage {
int32: 0,
};
msg.deserialize(&mut &v[..])?;
assert_eq!(150, msg.int32);
Numeric types
Numeric types work as you'd expect, using the default varint encoding.
#[derive(Serialize, Deserialize)]
struct TestMessage {
#[id(1)]
int32: i32,
#[id(2)]
int64: i64,
#[id(3)]
uint32: u32,
#[id(4)]
uint64: u64,
#[id(7)]
boolean: bool,
#[id(12)]
double: f64,
#[id(13)]
float: f32,
}
string/bytes fields can be represented as String (for string fields that are valid UTF-8),
or Vec<u8>, for bytes fields that may contain arbitrary data.
String types
#[derive(Serialize, Deserialize)]
struct TestMessage {
#[id(14)]
string: String,
#[id(16)]
bytes: Vec<u8>,
}
For numeric types, you can also specify alternate encodings, using the pbtype annotation.
Alternate encodings
#[derive(Serialize, Deserialize)]
struct TestMessage {
#[id(5)]
#[pbtype(sint32)]
sint32: i32,
#[id(6)]
#[pbtype(sint64)]
sint64: i64,
#[id(8)]
#[pbtype(fixed64)]
fixed64: u64,
#[id(9)]
#[pbtype(sfixed64)]
sfixed64: i64,
#[id(10)]
#[pbtype(fixed32)]
fixed32: u32,
#[id(11)]
#[pbtype(sfixed32)]
sfixed32: i32,
}
Enums
C-like enums are also supported, as long as they implement TryFrom for one of the primitive serializable types.
#[derive(Copy, Clone, Serialize, Deserialize)]
enum TestEnum {
VariantZero = 0,
VariantOne = 1,
VariantTwo = 2,
}
impl TryFrom<u64> for TestEnum {
type Error = std::io::Error;
fn try_from(value: u64) -> Result<Self, Self::Error> {
match value {
0 => Ok(TestEnum::VariantZero),
1 => Ok(TestEnum::VariantOne),
2 => Ok(TestEnum::VariantTwo),
_ => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("invalid TestEnum value: {}", value),
)),
}
}
}
#[derive(Serialize, Deserialize)]
struct TestMessage {
#[id(18)]
enumeration: TestEnum,
}
Submessages
Submessages are supported.
#[derive(Serialize, Deserialize)]
struct SubMessage {
#[id(1)]
int32: i32,
}
#[derive(Serialize, Deserialize)]
struct TestMessage {
#[id(19)]
submessage: SubMessage,
}
Repeated fields
Repeated fields can be represented with Vec.
#[derive(Serialize, Deserialize)]
struct TestMessage {
#[id(20)]
repeated: Vec<u32>,
}
Optional fields
To make fields optional, wrap them with Option. This prevents them from being serialized,
if they are None.
Note that it does not make sense to mark repeated fields as optional..
#[derive(Serialize)]
struct TestOptionalMessage {
#[id(1)]
int32: Option<i32>,
#[id(5)]
#[pbtype(sint32)]
sint32: Option<i32>,
#[id(14)]
string: Option<String>,
#[id(16)]
bytes: Option<Vec<u8>>,
#[id(18)]
enumeration: Option<TestEnum>,
#[id(19)]
submessage: Option<SubMessage>,
}