use std::{borrow::Cow, io::Read};
use anyhow::{self, format_err};
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
use chrono::{DateTime, NaiveDateTime, Utc};
use rust_decimal::Decimal;
use uuid::Uuid;
pub trait BinaryValue: Sized {
fn to_bytes(&self) -> Vec<u8>;
fn into_bytes(self) -> Vec<u8> {
self.to_bytes()
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> anyhow::Result<Self>;
}
macro_rules! impl_binary_value_scalar {
($type:tt, $read:ident) => {
#[allow(clippy::use_self)]
impl BinaryValue for $type {
fn to_bytes(&self) -> Vec<u8> {
vec![*self as u8]
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> anyhow::Result<Self> {
use byteorder::ReadBytesExt;
bytes.as_ref().$read().map_err(From::from)
}
}
};
($type:tt, $write:ident, $read:ident, $len:expr) => {
#[allow(clippy::use_self)]
impl BinaryValue for $type {
fn to_bytes(&self) -> Vec<u8> {
let mut v = vec![0; $len];
LittleEndian::$write(&mut v, *self);
v
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> anyhow::Result<Self> {
use byteorder::ReadBytesExt;
bytes.as_ref().$read::<LittleEndian>().map_err(From::from)
}
}
};
}
impl_binary_value_scalar! { u8, read_u8 }
impl_binary_value_scalar! { u16, write_u16, read_u16, 2 }
impl_binary_value_scalar! { u32, write_u32, read_u32, 4 }
impl_binary_value_scalar! { u64, write_u64, read_u64, 8 }
impl_binary_value_scalar! { u128, write_u128, read_u128, 16 }
impl_binary_value_scalar! { i8, read_i8 }
impl_binary_value_scalar! { i16, write_i16, read_i16, 2 }
impl_binary_value_scalar! { i32, write_i32, read_i32, 4 }
impl_binary_value_scalar! { i64, write_i64, read_i64, 8 }
impl_binary_value_scalar! { i128, write_i128, read_i128, 16 }
impl BinaryValue for () {
fn to_bytes(&self) -> Vec<u8> {
Vec::default()
}
fn from_bytes(_bytes: Cow<'_, [u8]>) -> anyhow::Result<Self> {
Ok(())
}
}
#[allow(clippy::use_self)] impl BinaryValue for bool {
fn to_bytes(&self) -> Vec<u8> {
vec![*self as u8]
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> anyhow::Result<Self> {
let value = bytes.as_ref();
assert_eq!(value.len(), 1);
match value[0] {
0 => Ok(false),
1 => Ok(true),
value => Err(format_err!("Invalid value for bool: {}", value)),
}
}
}
impl BinaryValue for Vec<u8> {
fn to_bytes(&self) -> Vec<u8> {
self.clone()
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> anyhow::Result<Self> {
Ok(bytes.into_owned())
}
}
impl BinaryValue for String {
fn to_bytes(&self) -> Vec<u8> {
self.as_bytes().to_owned()
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> anyhow::Result<Self> {
Self::from_utf8(bytes.into_owned()).map_err(From::from)
}
}
impl BinaryValue for DateTime<Utc> {
fn to_bytes(&self) -> Vec<u8> {
let secs = self.timestamp();
let nanos = self.timestamp_subsec_nanos();
let mut buffer = vec![0; 12];
LittleEndian::write_i64(&mut buffer[0..8], secs);
LittleEndian::write_u32(&mut buffer[8..12], nanos);
buffer
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> anyhow::Result<Self> {
let mut value = bytes.as_ref();
let secs = value.read_i64::<LittleEndian>()?;
let nanos = value.read_u32::<LittleEndian>()?;
Ok(Self::from_utc(
NaiveDateTime::from_timestamp(secs, nanos),
Utc,
))
}
}
impl BinaryValue for Uuid {
fn to_bytes(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> anyhow::Result<Self> {
Self::from_slice(bytes.as_ref()).map_err(From::from)
}
}
impl BinaryValue for Decimal {
fn to_bytes(&self) -> Vec<u8> {
self.serialize().to_vec()
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> anyhow::Result<Self> {
let mut value = bytes.as_ref();
let mut buf: [u8; 16] = [0; 16];
value.read_exact(&mut buf)?;
Ok(Self::deserialize(buf))
}
}
#[cfg(test)]
mod tests {
use std::fmt::Debug;
use std::str::FromStr;
use chrono::Duration;
use super::{BinaryValue, Decimal, Utc, Uuid};
fn assert_round_trip_eq<T: BinaryValue + PartialEq + Debug>(values: &[T]) {
for value in values {
let bytes = value.to_bytes();
assert_eq!(
*value,
<T as BinaryValue>::from_bytes(bytes.into()).unwrap()
);
}
}
macro_rules! impl_test_binary_form_scalar_unsigned {
($name:ident, $type:tt) => {
#[test]
fn $name() {
let values = [$type::min_value(), 1, $type::max_value()];
assert_round_trip_eq(&values);
}
};
}
macro_rules! impl_test_binary_form_scalar_signed {
($name:ident, $type:tt) => {
#[test]
fn $name() {
let values = [$type::min_value(), -1, 0, 1, $type::max_value()];
assert_round_trip_eq(&values);
}
};
}
impl_test_binary_form_scalar_unsigned! { test_binary_form_round_trip_u8, u8 }
impl_test_binary_form_scalar_unsigned! { test_binary_form_round_trip_u32, u32 }
impl_test_binary_form_scalar_unsigned! { test_binary_form_round_trip_u16, u16 }
impl_test_binary_form_scalar_unsigned! { test_binary_form_round_trip_u64, u64 }
impl_test_binary_form_scalar_unsigned! { test_binary_form_round_trip_u128, u128 }
impl_test_binary_form_scalar_signed! { test_binary_form_round_trip_i8, i8 }
impl_test_binary_form_scalar_signed! { test_binary_form_round_trip_i16, i16 }
impl_test_binary_form_scalar_signed! { test_binary_form_round_trip_i32, i32 }
impl_test_binary_form_scalar_signed! { test_binary_form_round_trip_i64, i64 }
impl_test_binary_form_scalar_signed! { test_binary_form_round_trip_i128, i128 }
#[test]
fn test_binary_form_vec_u8() {
let values = [vec![], vec![1], vec![1, 2, 3], vec![255; 100]];
assert_round_trip_eq(&values);
}
#[test]
fn test_binary_form_bool_correct() {
let values = [true, false];
assert_round_trip_eq(&values);
}
#[test]
#[should_panic(expected = "Invalid value for bool: 2")]
fn test_binary_form_bool_incorrect() {
let bytes = 2_u8.to_bytes();
<bool as BinaryValue>::from_bytes(bytes.into()).unwrap();
}
#[test]
fn test_binary_form_string() {
let values: Vec<_> = ["", "e", "2", "hello"]
.iter()
.map(ToString::to_string)
.collect();
assert_round_trip_eq(&values);
}
#[test]
fn test_binary_form_datetime() {
use chrono::TimeZone;
let times = [
Utc.timestamp(0, 0),
Utc.timestamp(13, 23),
Utc::now(),
Utc::now() + Duration::seconds(17) + Duration::nanoseconds(15),
Utc.timestamp(0, 999_999_999),
Utc.timestamp(0, 1_500_000_000), ];
assert_round_trip_eq(×);
}
#[test]
fn test_binary_form_uuid() {
let values = [
Uuid::nil(),
Uuid::parse_str("936DA01F9ABD4d9d80C702AF85C822A8").unwrap(),
Uuid::parse_str("0000002a-000c-0005-0c03-0938362b0809").unwrap(),
];
assert_round_trip_eq(&values);
}
#[test]
fn test_binary_form_decimal() {
let values = [
Decimal::from_str("3.14").unwrap(),
Decimal::from_parts(1_102_470_952, 185_874_565, 1_703_060_790, false, 28),
Decimal::new(9_497_628_354_687_268, 12),
Decimal::from_str("0").unwrap(),
Decimal::from_str("-0.000000000000000000019").unwrap(),
];
assert_round_trip_eq(&values);
}
}