# 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::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 {
// 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`.
```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 std::io::Read
) -> Result<(), DeserializeError>;
}
```
`Deerialize` types can be read from anything that implements `std::io::Read`.
```rust
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.
```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 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
```rust
#[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.
```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 deserializable 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>,
}
```