brotopuf

Crates.iobrotopuf
lib.rsbrotopuf
version0.1.1
created_at2025-12-30 03:28:17.45874+00
updated_at2025-12-30 22:27:42.926378+00
descriptionA library for (de)serializing structs in protocol buffer wire format
homepage
repositoryhttps://github.com/bklimt/brotopuf
max_upload_size
id2011992
size23,379
Bee Klimt (bklimt)

documentation

https://docs.rs/brotopuf

README

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::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

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 types

string/bytes fields can be represented as:

  • String — for string fields that are valid UTF-8
  • Vec<u8> — for bytes fields that may contain arbitrary data
#[derive(Serialize, Deserialize)]
struct TestMessage {
    #[id(14)]
    string: String,

    #[id(16)]
    bytes: Vec<u8>,
}

Alternate encodings

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,
}

Enums

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

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>,
}
Commit count: 0

cargo fmt