| Crates.io | brotopuf |
| lib.rs | brotopuf |
| version | 0.1.1 |
| created_at | 2025-12-30 03:28:17.45874+00 |
| updated_at | 2025-12-30 22:27:42.926378+00 |
| description | A library for (de)serializing structs in protocol buffer wire format |
| homepage | |
| repository | https://github.com/bklimt/brotopuf |
| max_upload_size | |
| id | 2011992 |
| size | 23,379 |
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.
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::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 {
// Serializes as a field inside an enclosing message.
fn serialize_field(
&self,
id: u64,
pbtype: ProtoType,
w: &mut impl std::io::Write
) -> std::io::Result<()>;
// Serializes as a standalone message.
fn serialize(
&self,
w: &mut impl std::io::Write
) -> std::io::Result<()>;
}
Serialize types can be written into anything that implements std::io::Write.
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 std::io::Read
) -> Result<(), DeserializeError>;
}
Deerialize types can be read from anything that implements std::io::Read.
let v: Vec<u8> = vec![
0x08, 0x96, 0x01, // int32
];
let mut msg = TestMessage {
int32: 0,
};
// from a mutable slice
msg.deserialize(&mut &v[..])?;
assert_eq!(150, msg.int32);
// from an immutable slice
let mut cursor = std::io::Cursor::new(&v[..]);
msg.deserialize(cursor.get_mut())?;
assert_eq!(150, msg.int32);
Numeric types work as you'd expect, using the default varint encoding for integers,
and IEEE 754 for floating point numbers.
#[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-8Vec<u8> — for bytes fields that may contain arbitrary data#[derive(Serialize, Deserialize)]
struct TestMessage {
#[id(14)]
string: String,
#[id(16)]
bytes: Vec<u8>,
}
For numeric types, you can specify alternate wire encodings, using the pbtype annotation.
#[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,
}
C-like enums are also supported, as long as they implement TryFrom for one of the primitive deserializable 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 are supported.
#[derive(Serialize, Deserialize)]
struct SubMessage {
#[id(1)]
int32: i32,
}
#[derive(Serialize, Deserialize)]
struct TestMessage {
#[id(19)]
submessage: SubMessage,
}
Repeated fields can be represented with Vec.
#[derive(Serialize, Deserialize)]
struct TestMessage {
#[id(20)]
repeated: Vec<u32>,
}
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>,
}