5 releases (3 breaking)
0.4.1 | Dec 24, 2023 |
---|---|
0.4.0 | Dec 24, 2023 |
0.3.1 | Dec 19, 2023 |
0.2.0 | May 10, 2021 |
0.1.0 | May 7, 2021 |
#60 in #binary-serialization
Used in lbs
26KB
390 lines
LBS
Library name stands for Lazy Binary Serialization. We call it lazy because it does not serizalize/deserialize struct fields of type Option<T>
when value is None
. When it comes to large structures with significant amount of optional fields this simple technique makes LBS faster than other libraries, where None
values must be somehow represented on wire anyway.
Safety
No unsafe code.
Status
API or format changes may be introduced until v1.0.0.
Usage
- Add
lbs = { version = "0.4.1", features = ["chrono", "smallvec", "ipnet", "uuid", "time"] }
toCargo.toml
. Remove features you don't need. - There are
LBSWrite
andLBSRead
traits which implementations can be derived for structs and enums with#[derive(LBSWrite, LBSRead)]
. - Each field or variant must have an attribute
#[lbs(id(<u16>))]
. This allows to change order of fields anytime and makes serialization cheaper. - If field is of type
Option<T>
and it's value isNone
, it is not serialized/deserialized, at all. Otherwise such a field is required unless it has explicit#[lbs(optional)]
attribute. - Each struct field's type must implement
Default
or such a field must have an attribute#[lbs(default(<expr>))]
. Even if field is required. This is because we don't want to use unsafe Rust to initialize structures. For now. - Struct field may be ignored using
#[lbs(skip)]
attribute. - Attributes may be concatenated like this:
#[lbs(id(<u16>), default(<expr>), skip, optional)]
.
#![allow(unused_imports, dead_code)]
use bytes::Buf;
use bytes::BufMut;
use bytes::Bytes;
use bytes::BytesMut;
use chrono::NaiveDate;
use ipnet::IpNet;
use lbs::error::LBSError;
use lbs::LBSRead;
use lbs::LBSWrite;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::collections::HashSet;
use std::net::IpAddr;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::ops::Range;
use std::rc::Rc;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use std::time::SystemTime;
use time::OffsetDateTime;
use uuid::Uuid;
#[derive(LBSWrite, LBSRead)]
struct StructOne<'a> {
#[lbs(id(0))]
f0: u8,
#[lbs(id(1))]
f1: u16,
#[lbs(id(2))]
f2: u32,
#[lbs(id(3))]
f3: u64,
#[lbs(id(4))]
f4: usize,
#[lbs(id(5))]
f5: u128,
#[lbs(id(6))]
f6: i8,
#[lbs(id(7))]
f7: i16,
#[lbs(id(8))]
f8: i32,
#[lbs(id(9))]
f9: i64,
#[lbs(id(10))]
f10: isize,
#[lbs(id(11))]
f11: i128,
#[lbs(id(12))]
f12: f32,
#[lbs(id(13))]
f13: f64,
#[lbs(id(14))]
f14: (),
#[lbs(id(15))]
f15: (u64, String),
#[lbs(id(16))]
f16: (u64, u64, u64),
#[lbs(id(17))]
f17: bool,
#[lbs(id(18))]
f18: char,
#[lbs(id(19))]
f19: String,
#[lbs(id(20))]
f20: Duration,
#[lbs(id(21), default(SystemTime::UNIX_EPOCH))]
f21: SystemTime,
#[lbs(id(22), default(Ipv4Addr::UNSPECIFIED))]
f22: Ipv4Addr,
#[lbs(id(23), default(Ipv6Addr::UNSPECIFIED))]
f23: Ipv6Addr,
#[lbs(id(24), default(IpAddr::V4(Ipv4Addr::UNSPECIFIED)))]
f24: IpAddr,
#[lbs(id(25))]
f25: Range<u64>,
#[lbs(id(26))]
f26: Vec<u64>,
#[lbs(id(27))]
f27: Rc<String>,
#[lbs(id(28))]
f28: Arc<String>,
#[lbs(id(29), default(Arc::from("")))]
f29: Arc<str>,
#[lbs(id(30))]
f30: Cow<'a, str>,
#[lbs(id(31))]
f31: Option<String>,
#[lbs(id(32))]
f32: Vec<String>,
#[lbs(id(33))]
f33: HashMap<String, u64>,
#[lbs(id(34))]
f34: BTreeMap<u64, String>,
#[lbs(id(35))]
f35: HashSet<String>,
#[lbs(id(36))]
f36: BTreeSet<u64>,
#[lbs(id(37))]
f37: chrono::DateTime<chrono::Utc>,
#[lbs(id(38))]
f38: smallvec::SmallVec<[i64; 4]>,
#[lbs(id(39))]
f39: StructTwo,
#[lbs(id(40))]
f40: EnumOne,
#[lbs(id(41))]
f41: IpNet,
#[lbs(id(42))]
f42: Uuid,
#[lbs(id(43), default(OffsetDateTime::UNIX_EPOCH))]
f43: OffsetDateTime,
#[lbs(id(44), skip)]
f44: bool,
}
// Field IDs are assigned implicitly, using their index
#[derive(LBSWrite, LBSRead, Default, PartialEq, Debug)]
struct StructTwo {
#[lbs(id(0))]
id: Uuid,
#[lbs(id(1))]
name: String,
#[lbs(id(2))]
en: Option<EnumOne>,
}
// Variant IDs are assigned implicitly, using their index
#[derive(LBSWrite, LBSRead, PartialEq, Debug, Default)]
enum EnumOne {
#[default]
#[lbs(id(0))]
One,
#[lbs(id(1))]
Two,
#[lbs(id(2))]
Three(String),
#[lbs(id(3))]
Four(EnumTwo),
}
#[derive(LBSWrite, LBSRead, PartialEq, Debug)]
enum EnumTwo {
#[lbs(id(0))]
One,
#[lbs(id(1))]
Two,
}
#[test]
fn usage() {
let mut original = StructOne {
f0: 1,
f1: 1,
f2: 2,
f3: 3,
f4: 4,
f5: 5,
f6: 1,
f7: -1,
f8: -2,
f9: -3,
f10: 23,
f11: 1,
f12: 1.1,
f13: -3.14,
f14: (),
f15: (1, String::from("1")),
f16: (1, 2, 3),
f17: true,
f18: 'a',
f19: String::from("test"),
f20: Duration::from_millis(1000),
f21: SystemTime::now(),
f22: Ipv4Addr::new(192, 168, 1, 2),
f23: Ipv6Addr::from_str("2001:0db8:85a3:0000:0000:8a2e:0370:7334").unwrap(),
f24: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
f25: Range { start: 0, end: 1 },
f26: vec![1, 2, 3],
f27: Rc::new(String::from("test_rc")),
f28: Arc::new(String::from("test_arc")),
f29: Arc::from("test_str_arc"),
f30: Cow::Owned(String::from("test_cow")),
f31: Some("str".to_string()),
f32: Vec::new(),
f33: HashMap::new(),
f34: BTreeMap::new(),
f35: HashSet::new(),
f36: BTreeSet::new(),
f37: chrono::Utc::now(),
f38: smallvec::smallvec![0, 1],
f39: StructTwo::default(),
f40: EnumOne::Three(String::from("test_enum")),
f41: IpNet::from_str("192.168.1.0/24").unwrap(),
f42: Uuid::new_v4(),
f43: OffsetDateTime::now_utc(),
f44: true,
};
original.f33.insert(String::from("key1"), 1);
original.f33.insert(String::from("key2"), 2);
original.f34.insert(1, String::from("key1"));
original.f34.insert(2, String::from("key2"));
original.f35.insert(String::from("key1"));
original.f35.insert(String::from("key2"));
original.f36.insert(1);
original.f36.insert(1);
let mut buf = Vec::with_capacity(128);
original.lbs_write(&mut buf).unwrap();
let decoded = StructOne::lbs_read(&mut buf.as_slice()).unwrap();
assert_eq!(decoded.f0, original.f0);
assert_eq!(decoded.f1, original.f1);
assert_eq!(decoded.f2, original.f2);
assert_eq!(decoded.f3, original.f3);
assert_eq!(decoded.f4, original.f4);
assert_eq!(decoded.f5, original.f5);
assert_eq!(decoded.f6, original.f6);
assert_eq!(decoded.f7, original.f7);
assert_eq!(decoded.f8, original.f8);
assert_eq!(decoded.f9, original.f9);
assert_eq!(decoded.f10, original.f10);
assert_eq!(decoded.f11, original.f11);
assert_eq!(decoded.f12, original.f12);
assert_eq!(decoded.f13, original.f13);
assert_eq!(decoded.f14, original.f14);
assert_eq!(decoded.f15, original.f15);
assert_eq!(decoded.f16, original.f16);
assert_eq!(decoded.f17, original.f17);
assert_eq!(decoded.f18, original.f18);
assert_eq!(decoded.f19, original.f19);
assert_eq!(decoded.f20, original.f20);
assert_eq!(decoded.f21, original.f21);
assert_eq!(decoded.f22, original.f22);
assert_eq!(decoded.f23, original.f23);
assert_eq!(decoded.f24, original.f24);
assert_eq!(decoded.f25, original.f25);
assert_eq!(decoded.f26, original.f26);
assert_eq!(decoded.f27, original.f27);
assert_eq!(decoded.f28, original.f28);
assert_eq!(decoded.f29, original.f29);
assert_eq!(decoded.f30, original.f30);
assert_eq!(decoded.f31, original.f31);
assert_eq!(decoded.f32, original.f32);
assert_eq!(decoded.f33, original.f33);
assert_eq!(decoded.f34, original.f34);
assert_eq!(decoded.f35, original.f35);
assert_eq!(decoded.f36, original.f36);
assert_eq!(decoded.f37, original.f37);
assert_eq!(decoded.f38, original.f38);
assert_eq!(decoded.f39, original.f39);
assert_eq!(decoded.f40, original.f40);
assert_eq!(decoded.f41, original.f41);
assert_eq!(decoded.f42, original.f42);
assert_eq!(decoded.f43, original.f43);
assert_eq!(decoded.f44, false);
}
#[derive(LBSWrite, LBSRead, PartialEq, Debug)]
struct MessageV1 {
#[lbs(id(0))]
f0: u64,
#[lbs(id(1))]
f1: Option<u64>,
}
#[derive(LBSWrite, LBSRead, PartialEq, Debug)]
struct MessageV2 {
#[lbs(id(0))]
f0: u64,
#[lbs(id(1))]
f1: Option<u64>,
#[lbs(id(2))]
f2: u64,
}
#[test]
fn required() {
let msgv1 = MessageV1 { f0: 1, f1: None };
let mut buf = Vec::with_capacity(128);
msgv1.lbs_write(&mut buf).unwrap();
if let Err(e) = MessageV2::lbs_read(&mut buf.as_slice()) {
if let LBSError::WithField(id, inner) = e {
if let LBSError::RequiredButMissing = inner.as_ref() {
if id == 2 {
return;
}
}
}
}
panic!("not an error");
}
#[derive(LBSWrite, LBSRead, PartialEq, Debug)]
struct OtherMessageV1 {
#[lbs(id(0))]
f0: u64,
#[lbs(id(1))]
f1: Option<u64>,
}
#[derive(LBSWrite, LBSRead, PartialEq, Debug)]
struct OtherMessageV2 {
#[lbs(id(0))]
f0: u64,
#[lbs(id(1))]
f1: Option<u64>,
#[lbs(id(2), optional)]
f2: u64,
}
#[test]
fn optional() {
let msgv1 = OtherMessageV1 { f0: 1, f1: None };
let mut buf = Vec::with_capacity(128);
msgv1.lbs_write(&mut buf).unwrap();
OtherMessageV2::lbs_read(&mut buf.as_slice()).unwrap();
}
Dependencies
~240–690KB
~16K SLoC