# brotopuf
brotopuf is a library for serializing and deserializing structs using the
[protocol buffer wire format](https://protobuf.dev/programming-guides/encoding/).
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.
```rust
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.
```rust
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<()>;
}
```
```rust
let mut v: Vec<u8> = Vec::new();
s.serialize(&mut v).unwrap();
```
Likewise for `Deserialize`:
```rust
pub trait Deserialize {
fn deserialize(&mut self, r: &mut impl Read) -> Result<(), DeserializeError>;
}
```
```rust
let v: Vec<u8> = vec![
0x08, 0x96, 0x01, // int32
];
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.
```rust
#[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
```rust
#[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
```rust
#[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.
```rust
#[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.
```rust
#[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`.
```rust
#[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`..
```rust
#[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>,
}
```