use std::io::Write;
use super::consts::{FANTOM_EPOCH_UNIX_SECS, lookup_const};
use crate::haystack::val::{
Bool, Coord, Date, DateTime, Dict, Grid, List, Marker, Na, Number, Ref, Remove, Str, Symbol,
Time, Uri, Value, XStr,
};
pub const CTRL_NULL: u8 = 0x00;
pub const CTRL_MARKER: u8 = 0x01;
pub const CTRL_NA: u8 = 0x02;
pub const CTRL_REMOVE: u8 = 0x03;
pub const CTRL_FALSE: u8 = 0x04;
pub const CTRL_TRUE: u8 = 0x05;
pub const CTRL_NUMBER_I2: u8 = 0x06;
pub const CTRL_NUMBER_I4: u8 = 0x07;
pub const CTRL_NUMBER_F8: u8 = 0x08;
pub const CTRL_STR: u8 = 0x09;
pub const CTRL_REF_STR: u8 = 0x0a;
pub const CTRL_REF_I8: u8 = 0x0b;
pub const CTRL_URI: u8 = 0x0c;
pub const CTRL_DATE: u8 = 0x0d;
pub const CTRL_TIME: u8 = 0x0e;
pub const CTRL_DATETIME_I4: u8 = 0x0f;
pub const CTRL_DATETIME_I8: u8 = 0x10;
pub const CTRL_COORD: u8 = 0x11;
pub const CTRL_XSTR: u8 = 0x12;
pub const CTRL_BUF: u8 = 0x13;
pub const CTRL_DICT_EMPTY: u8 = 0x14;
pub const CTRL_DICT: u8 = 0x15;
pub const CTRL_LIST_EMPTY: u8 = 0x16;
pub const CTRL_LIST: u8 = 0x17;
pub const CTRL_GRID: u8 = 0x18;
pub const CTRL_SYMBOL: u8 = 0x19;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Error {
Message(String),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Message(msg) => write!(f, "{msg}"),
}
}
}
impl std::error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Message(err.to_string())
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub trait ToBrio {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()>;
fn to_brio_vec(&self) -> Result<Vec<u8>> {
let mut buf = Vec::new();
self.to_brio(&mut buf)?;
Ok(buf)
}
}
pub fn encode_varint<W: Write>(writer: &mut W, val: i64) -> Result<()> {
if val < 0 {
writer.write_all(&[0xff])?;
} else if val <= 0x7f {
writer.write_all(&[val as u8])?;
} else if val <= 0x3fff {
let encoded = val as u16 | 0x8000;
writer.write_all(&encoded.to_be_bytes())?;
} else if val <= 0x1fff_ffff {
let encoded = val as u32 | 0xc000_0000;
writer.write_all(&encoded.to_be_bytes())?;
} else {
writer.write_all(&[0xe0])?;
writer.write_all(&val.to_be_bytes())?;
}
Ok(())
}
pub fn encode_str<W: Write>(writer: &mut W, s: &str) -> Result<()> {
if let Some(idx) = lookup_const(s) {
encode_varint(writer, idx)?;
} else {
encode_varint(writer, -1)?; encode_str_chars(writer, s)?;
}
Ok(())
}
fn encode_str_chars<W: Write>(writer: &mut W, s: &str) -> Result<()> {
let units: Vec<u16> = s.encode_utf16().collect();
encode_varint(writer, units.len() as i64)?;
for &unit in &units {
write_cesu8_unit(writer, unit)?;
}
Ok(())
}
fn write_cesu8_unit<W: Write>(writer: &mut W, unit: u16) -> Result<()> {
match unit {
0x0000 => writer.write_all(&[0xC0, 0x80])?,
0x0001..=0x007F => writer.write_all(&[unit as u8])?,
0x0080..=0x07FF => {
writer.write_all(&[0xC0 | (unit >> 6) as u8, 0x80 | (unit & 0x3F) as u8])?
}
0x0800..=0xFFFF => writer.write_all(&[
0xE0 | (unit >> 12) as u8,
0x80 | ((unit >> 6) & 0x3F) as u8,
0x80 | (unit & 0x3F) as u8,
])?,
}
Ok(())
}
fn ref_id_to_i8(id: &str) -> Option<i64> {
let bytes = id.as_bytes();
if bytes.len() != 17 || bytes[8] != b'-' {
return None;
}
let mut val: i64 = 0;
for (i, &b) in bytes.iter().enumerate() {
if i == 8 {
continue;
}
let digit = (b as char).to_digit(16)? as i64;
val = (val << 4) | digit;
}
if val < 0 { None } else { Some(val) }
}
impl ToBrio for Value {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
match self {
Value::Null => writer.write_all(&[CTRL_NULL])?,
Value::Marker => Marker.to_brio(writer)?,
Value::Na => Na.to_brio(writer)?,
Value::Remove => Remove.to_brio(writer)?,
Value::Bool(v) => v.to_brio(writer)?,
Value::Number(v) => v.to_brio(writer)?,
Value::Str(v) => v.to_brio(writer)?,
Value::Ref(v) => v.to_brio(writer)?,
Value::Uri(v) => v.to_brio(writer)?,
Value::Date(v) => v.to_brio(writer)?,
Value::Time(v) => v.to_brio(writer)?,
Value::DateTime(v) => v.to_brio(writer)?,
Value::Coord(v) => v.to_brio(writer)?,
Value::XStr(v) => v.to_brio(writer)?,
Value::Symbol(v) => v.to_brio(writer)?,
Value::List(v) => v.to_brio(writer)?,
Value::Dict(v) => v.to_brio(writer)?,
Value::Grid(v) => v.to_brio(writer)?,
}
Ok(())
}
}
impl ToBrio for Marker {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&[CTRL_MARKER])?;
Ok(())
}
}
impl ToBrio for Na {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&[CTRL_NA])?;
Ok(())
}
}
impl ToBrio for Remove {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&[CTRL_REMOVE])?;
Ok(())
}
}
impl ToBrio for Bool {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&[if self.value { CTRL_TRUE } else { CTRL_FALSE }])?;
Ok(())
}
}
impl ToBrio for Str {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&[CTRL_STR])?;
encode_str(writer, &self.value)?;
Ok(())
}
}
impl ToBrio for Uri {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&[CTRL_URI])?;
encode_str(writer, &self.value)?;
Ok(())
}
}
impl ToBrio for Symbol {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&[CTRL_SYMBOL])?;
encode_str(writer, &self.value)?;
Ok(())
}
}
impl ToBrio for Number {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
let v = self.value;
let unit_str = self.unit.map_or("", |u| u.symbol());
if v.fract() == 0.0 && v >= -(i16::MAX as f64) && v <= i16::MAX as f64 {
writer.write_all(&[CTRL_NUMBER_I2])?;
writer.write_all(&(v as i16).to_be_bytes())?;
} else if v.fract() == 0.0 && v >= i32::MIN as f64 && v <= i32::MAX as f64 {
writer.write_all(&[CTRL_NUMBER_I4])?;
writer.write_all(&(v as i32).to_be_bytes())?;
} else {
writer.write_all(&[CTRL_NUMBER_F8])?;
writer.write_all(&v.to_be_bytes())?;
}
encode_str(writer, unit_str)?;
Ok(())
}
}
impl ToBrio for Ref {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
let dis = self.dis.as_deref().unwrap_or("");
if let Some(i8_val) = ref_id_to_i8(&self.value) {
writer.write_all(&[CTRL_REF_I8])?;
writer.write_all(&i8_val.to_be_bytes())?;
} else {
writer.write_all(&[CTRL_REF_STR])?;
encode_str(writer, &self.value)?;
}
encode_str_chars(writer, dis)?;
Ok(())
}
}
impl ToBrio for Date {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
use chrono::Datelike;
writer.write_all(&[CTRL_DATE])?;
writer.write_all(&(self.year() as i16).to_be_bytes())?;
writer.write_all(&[self.month() as u8])?;
writer.write_all(&[self.day() as u8])?;
Ok(())
}
}
impl ToBrio for Time {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
use chrono::Timelike;
writer.write_all(&[CTRL_TIME])?;
let millis = self.hour() * 3_600_000
+ self.minute() * 60_000
+ self.second() * 1_000
+ self.nanosecond() / 1_000_000;
writer.write_all(&(millis as i32).to_be_bytes())?;
Ok(())
}
}
impl ToBrio for DateTime {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
let unix_secs = self.timestamp();
let nanos = self.timestamp_subsec_nanos();
let fantom_secs = unix_secs - FANTOM_EPOCH_UNIX_SECS;
let tz = self.timezone_short_name();
if nanos == 0 && fantom_secs >= i32::MIN as i64 && fantom_secs <= i32::MAX as i64 {
writer.write_all(&[CTRL_DATETIME_I4])?;
writer.write_all(&(fantom_secs as i32).to_be_bytes())?;
} else {
let fantom_nanos = (unix_secs - FANTOM_EPOCH_UNIX_SECS) * 1_000_000_000 + nanos as i64;
writer.write_all(&[CTRL_DATETIME_I8])?;
writer.write_all(&fantom_nanos.to_be_bytes())?;
}
encode_str(writer, &tz)?;
Ok(())
}
}
impl ToBrio for Coord {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&[CTRL_COORD])?;
let pack_lat = ((self.lat + 90.0) * 1_000_000.0_f64).round() as i32;
let pack_lng = ((self.long + 180.0) * 1_000_000.0_f64).round() as i32;
writer.write_all(&pack_lat.to_be_bytes())?;
writer.write_all(&pack_lng.to_be_bytes())?;
Ok(())
}
}
impl ToBrio for XStr {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&[CTRL_XSTR])?;
encode_str(writer, &self.r#type)?;
encode_str(writer, &self.value)?;
Ok(())
}
}
impl ToBrio for Dict {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
let non_null_count = self
.iter()
.filter(|(_, v)| !matches!(v, Value::Null))
.count();
if non_null_count == 0 {
writer.write_all(&[CTRL_DICT_EMPTY])?;
} else {
writer.write_all(&[CTRL_DICT, b'{'])?;
encode_varint(writer, non_null_count as i64)?;
for (key, val) in self.iter() {
if matches!(val, Value::Null) {
continue;
}
encode_str(writer, key)?;
val.to_brio(writer)?;
}
writer.write_all(b"}")?;
}
Ok(())
}
}
impl ToBrio for List {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
if self.is_empty() {
writer.write_all(&[CTRL_LIST_EMPTY])?;
} else {
writer.write_all(&[CTRL_LIST, b'['])?;
encode_varint(writer, self.len() as i64)?;
for val in self.iter() {
val.to_brio(writer)?;
}
writer.write_all(b"]")?;
}
Ok(())
}
}
impl ToBrio for Grid {
fn to_brio<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&[CTRL_GRID, b'<'])?;
encode_varint(writer, self.columns.len() as i64)?;
encode_varint(writer, self.rows.len() as i64)?;
match &self.meta {
Some(meta) => meta.to_brio(writer)?,
None => writer.write_all(&[CTRL_DICT_EMPTY])?,
}
for col in &self.columns {
encode_str(writer, &col.name)?;
match &col.meta {
Some(meta) => meta.to_brio(writer)?,
None => writer.write_all(&[CTRL_DICT_EMPTY])?,
}
}
for row in &self.rows {
for col in &self.columns {
match row.get(&col.name) {
Some(val) => val.to_brio(writer)?,
None => writer.write_all(&[CTRL_NULL])?,
}
}
}
writer.write_all(b">")?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dict;
use crate::haystack::val::*;
use crate::units::{Unit, get_unit_or_default};
fn enc(v: &Value) -> Vec<u8> {
v.to_brio_vec().expect("encode")
}
#[test]
fn test_null() {
assert_eq!(enc(&Value::Null), vec![CTRL_NULL]);
}
#[test]
fn test_marker() {
assert_eq!(enc(&Value::make_marker()), vec![CTRL_MARKER]);
}
#[test]
fn test_na() {
assert_eq!(enc(&Value::make_na()), vec![CTRL_NA]);
}
#[test]
fn test_remove() {
assert_eq!(enc(&Value::make_remove()), vec![CTRL_REMOVE]);
}
#[test]
fn test_bool_false() {
assert_eq!(enc(&Value::from(false)), vec![CTRL_FALSE]);
}
#[test]
fn test_bool_true() {
assert_eq!(enc(&Value::from(true)), vec![CTRL_TRUE]);
}
#[test]
fn test_number_i2() {
let v = Value::from(Number::make(42.0));
let bytes = enc(&v);
assert_eq!(bytes[0], CTRL_NUMBER_I2);
assert_eq!(i16::from_be_bytes([bytes[1], bytes[2]]), 42);
}
#[test]
fn test_number_i4() {
let v = Value::from(Number::make(100_000.0));
let bytes = enc(&v);
assert_eq!(bytes[0], CTRL_NUMBER_I4);
assert_eq!(
i32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]),
100_000
);
}
#[test]
fn test_number_f8() {
let v = Value::from(Number::make(std::f64::consts::PI));
let bytes = enc(&v);
assert_eq!(bytes[0], CTRL_NUMBER_F8);
let f = f64::from_be_bytes(bytes[1..9].try_into().unwrap());
assert!((f - std::f64::consts::PI).abs() < 1e-10);
}
#[test]
fn test_number_with_unit() {
let v = Value::from(Number::make_with_unit(100.0, get_unit_or_default("kW")));
let bytes = enc(&v);
assert_eq!(bytes[0], CTRL_NUMBER_I2);
}
#[test]
fn test_varint_ranges() {
let mut buf = Vec::new();
encode_varint(&mut buf, -1).unwrap();
assert_eq!(buf, vec![0xff]);
buf.clear();
encode_varint(&mut buf, 0).unwrap();
assert_eq!(buf, vec![0x00]);
buf.clear();
encode_varint(&mut buf, 0x7f).unwrap();
assert_eq!(buf, vec![0x7f]);
buf.clear();
encode_varint(&mut buf, 0x80).unwrap();
assert_eq!(buf, vec![0x80, 0x80]);
buf.clear();
encode_varint(&mut buf, 0x3fff).unwrap();
assert_eq!(buf, vec![0xbf, 0xff]);
buf.clear();
encode_varint(&mut buf, 0x4000).unwrap();
assert_eq!(buf, vec![0xc0, 0x00, 0x40, 0x00]);
}
#[test]
fn test_str_inline_unknown() {
let v = Value::from("hello");
let bytes = enc(&v);
assert_eq!(bytes[0], CTRL_STR);
assert_eq!(bytes[1], 0xff);
assert_eq!(bytes[2], 5);
assert_eq!(&bytes[3..], b"hello");
}
#[test]
fn test_str_const_encoding() {
let v = Value::from("dis");
let bytes = enc(&v);
assert_eq!(bytes, vec![CTRL_STR, 0x80, 0xb9]);
}
#[test]
fn test_dict_key_uses_const() {
let dict_val = Value::make_dict(dict! { "dis" => Value::make_marker() });
let bytes_with_consts = enc(&dict_val);
assert!(
bytes_with_consts.len() < 10,
"const encoding should be shorter than inline"
);
}
#[test]
fn test_unit_uses_const() {
use crate::haystack::encoding::brio::consts::lookup_const;
assert_eq!(lookup_const("kW"), Some(630));
let v = Value::from(Number::make_with_unit(50.0, get_unit_or_default("kW")));
let bytes = enc(&v);
assert_eq!(bytes[3], 0x82);
assert_eq!(bytes[4], 0x76);
}
#[test]
fn test_timezone_uses_const() {
let dt = DateTime::parse_from_rfc3339("2021-06-15T12:00:00Z").unwrap();
let bytes = enc(&Value::from(dt));
assert_eq!(bytes.len(), 6);
assert_eq!(bytes[0], CTRL_DATETIME_I4);
assert_eq!(bytes[5], 24); }
#[test]
fn test_str_cesu8_null_char() {
let v = Value::from("\0");
let bytes = enc(&v);
assert_eq!(bytes, vec![CTRL_STR, 0xff, 0x01, 0xC0, 0x80]);
}
#[test]
fn test_str_cesu8_supplementary_char() {
let v = Value::from("\u{1F600}");
let bytes = enc(&v);
assert_eq!(
bytes,
vec![
CTRL_STR, 0xff, 0x02, 0xED, 0xA0, 0xBD, 0xED, 0xB8, 0x80, ]
);
}
#[test]
fn test_str_cesu8_round_trip() {
let original = Value::from("a\0b\u{1F600}c");
let bytes = enc(&original);
let decoded = crate::encoding::brio::decode::from_brio(&mut bytes.as_slice()).unwrap();
assert_eq!(original, decoded);
}
#[test]
fn test_uri() {
let v = Value::from(Uri {
value: "http://example.com".into(),
});
let bytes = enc(&v);
assert_eq!(bytes[0], CTRL_URI);
}
#[test]
fn test_ref_str() {
let r = Ref::make("abc123", None);
let bytes = enc(&Value::from(r));
assert_eq!(bytes[0], CTRL_REF_STR);
}
#[test]
fn test_ref_i8() {
let r = Ref::make("cafebabe-deadbeef", None);
let bytes = enc(&Value::from(r));
assert_eq!(bytes[0], CTRL_REF_STR);
}
#[test]
fn test_date() {
let d = Date::from_ymd(2021, 6, 15).unwrap();
let bytes = enc(&Value::from(d));
assert_eq!(bytes[0], CTRL_DATE);
assert_eq!(i16::from_be_bytes([bytes[1], bytes[2]]), 2021);
assert_eq!(bytes[3], 6);
assert_eq!(bytes[4], 15);
}
#[test]
fn test_time() {
let t = Time::from_hms(10, 30, 0).unwrap();
let bytes = enc(&Value::from(t));
assert_eq!(bytes[0], CTRL_TIME);
let millis = i32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
assert_eq!(millis, (10 * 3600 + 30 * 60) * 1000);
}
#[test]
fn test_datetime_i4() {
let dt = DateTime::parse_from_rfc3339("2021-06-15T12:00:00Z").unwrap();
let bytes = enc(&Value::from(dt));
assert_eq!(bytes[0], CTRL_DATETIME_I4);
}
#[test]
fn test_coord() {
let c = Coord::make(45.0, 23.0);
let bytes = enc(&Value::from(c));
assert_eq!(bytes[0], CTRL_COORD);
let lat = i32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
let lng = i32::from_be_bytes([bytes[5], bytes[6], bytes[7], bytes[8]]);
assert_eq!(lat, 135_000_000); assert_eq!(lng, 203_000_000); }
#[test]
fn test_xstr() {
let x = XStr::make("Blob", "data");
let bytes = enc(&Value::from(x));
assert_eq!(bytes[0], CTRL_XSTR);
}
#[test]
fn test_symbol() {
let s = Symbol::make("foo");
let bytes = enc(&Value::from(s));
assert_eq!(bytes[0], CTRL_SYMBOL);
}
#[test]
fn test_empty_dict() {
let d = Dict::default();
let bytes = enc(&Value::from(d));
assert_eq!(bytes, vec![CTRL_DICT_EMPTY]);
}
#[test]
fn test_non_empty_dict() {
let d = dict! { "marker" => Value::make_marker() };
let bytes = enc(&Value::make_dict(d));
assert_eq!(bytes[0], CTRL_DICT);
assert_eq!(bytes[1], b'{');
}
#[test]
fn test_empty_list() {
let l: List = vec![];
let bytes = enc(&Value::from(l));
assert_eq!(bytes, vec![CTRL_LIST_EMPTY]);
}
#[test]
fn test_non_empty_list() {
let l: List = vec![Value::make_marker(), Value::from(true)];
let bytes = enc(&Value::from(l));
assert_eq!(bytes[0], CTRL_LIST);
assert_eq!(bytes[1], b'[');
}
#[test]
fn test_grid() {
let g = Grid::make_from_dicts(vec![dict! { "dis" => Value::from("Site") }]);
let bytes = enc(&Value::from(g));
assert_eq!(bytes[0], CTRL_GRID);
assert_eq!(bytes[1], b'<');
}
fn enc_size(v: &Value) -> usize {
enc(v).len()
}
fn varint_size(val: i64) -> usize {
let mut buf: Vec<u8> = Vec::new();
encode_varint(&mut buf, val).unwrap();
buf.len()
}
fn make_custom_unit(symbol: &'static str) -> &'static Unit {
Box::leak(Box::new(Unit {
quantity: None,
ids: vec![symbol.into()].into(),
dimensions: None,
scale: 1.0,
offset: 0.0,
}))
}
#[test]
fn test_haxall_compat_scalar_sizes() {
assert_eq!(enc_size(&Value::Null), 1);
assert_eq!(enc_size(&Value::make_marker()), 1);
assert_eq!(enc_size(&Value::make_na()), 1);
assert_eq!(enc_size(&Value::make_remove()), 1);
assert_eq!(enc_size(&Value::from(true)), 1);
assert_eq!(enc_size(&Value::from(false)), 1);
}
#[test]
fn test_haxall_compat_number_sizes() {
assert_eq!(enc_size(&Value::from(Number::make(12.0))), 4);
assert_eq!(enc_size(&Value::from(Number::make(123_456_789.0))), 6);
assert_eq!(
enc_size(&Value::from(Number::make_with_unit(
123_456_789.0,
get_unit_or_default("°F")
))),
7
);
assert_eq!(
enc_size(&Value::from(Number::make_with_unit(
123_456.789,
get_unit_or_default("°F")
))),
11
);
assert_eq!(enc_size(&Value::from(Number::make(32767.0))), 4);
assert_eq!(enc_size(&Value::from(Number::make(32768.0))), 6);
assert_eq!(enc_size(&Value::from(Number::make(-32767.0))), 4);
assert_eq!(enc_size(&Value::from(Number::make(-32768.0))), 6);
assert_eq!(enc_size(&Value::from(Number::make(2_147_483_647.0))), 6);
assert_eq!(enc_size(&Value::from(Number::make(2_147_483_648.0))), 10);
assert_eq!(enc_size(&Value::from(Number::make(-2_147_483_648.0))), 6);
assert_eq!(enc_size(&Value::from(Number::make(-2_147_483_649.0))), 10);
}
#[test]
fn test_haxall_compat_string_sizes() {
assert_eq!(enc_size(&Value::from("")), 2);
assert_eq!(enc_size(&Value::from("hello °F world!")), 19);
assert_eq!(enc_size(&Value::from("siteRef")), 3);
assert_eq!(enc_size(&Value::from("New_York")), 2);
}
#[test]
fn test_haxall_compat_ref_sizes() {
assert_eq!(
enc_size(&Value::from(Ref::make("1deb31b8-7508b187", None))),
10
);
assert_eq!(
enc_size(&Value::from(Ref::make("1debX1b8-7508b187", None))),
21
);
assert_eq!(
enc_size(&Value::from(Ref::make("1deb31b8.7508b187", None))),
21
);
assert_eq!(
enc_size(&Value::from(Ref::make("1deb31b8-7508b187", Some("hi!")))),
13
);
}
#[test]
fn test_haxall_compat_symbol_sizes() {
assert_eq!(enc_size(&Value::from(Symbol::make("coolingTower"))), 3);
assert_eq!(enc_size(&Value::from(Symbol::make("foo-bar"))), 10);
}
#[test]
fn test_haxall_compat_container_sizes() {
assert_eq!(enc_size(&Value::from(Dict::default())), 1);
assert_eq!(enc_size(&Value::from(vec![] as List)), 1);
assert_eq!(enc_size(&Value::from(Coord::make(37.54, 77.43))), 9);
}
#[test]
fn test_haxall_compat_varint_sizes() {
assert_eq!(varint_size(-1), 1);
assert_eq!(varint_size(0), 1);
assert_eq!(varint_size(30), 1);
assert_eq!(varint_size(64), 1);
assert_eq!(varint_size(127), 1);
assert_eq!(varint_size(128), 2);
assert_eq!(varint_size(1_000), 2);
assert_eq!(varint_size(16_383), 2);
assert_eq!(varint_size(16_384), 4);
assert_eq!(varint_size(500_123), 4);
assert_eq!(varint_size(536_870_911), 4);
assert_eq!(varint_size(536_870_912), 9);
assert_eq!(varint_size(123_456_789_123), 9);
}
#[test]
fn test_haxall_compat_number_nonconstunit() {
assert_eq!(
enc_size(&Value::from(Number::make_with_unit(
123_456_789.0,
make_custom_unit("_foo")
))),
11
);
}
#[test]
fn test_haxall_compat_coord_negative() {
assert_eq!(enc_size(&Value::from(Coord::make(-17.535, -149.569))), 9);
}
#[test]
fn test_haxall_compat_datetime_sizes() {
fn dt_size(iso: &str, tz: &str) -> usize {
let dt = DateTime::parse_from_rfc3339_with_timezone(iso, tz).unwrap();
enc_size(&Value::from(dt))
}
assert_eq!(dt_size("2015-11-30T12:03:57-05:00", "New_York"), 6);
assert_eq!(dt_size("2015-11-30T12:02:33.378-05:00", "New_York"), 10);
assert_eq!(dt_size("2015-11-30T12:03:57.000123-05:00", "New_York"), 10);
assert_eq!(dt_size("2000-01-01T00:00:00+01:00", "Warsaw"), 13);
assert_eq!(dt_size("2000-01-01T00:00:00.832+01:00", "Warsaw"), 17);
assert_eq!(dt_size("1999-06-07T01:02:00-04:00", "New_York"), 6);
assert_eq!(dt_size("1950-06-07T01:02:00-04:00", "New_York"), 6);
assert_eq!(dt_size("1950-06-07T01:02:00.123-04:00", "New_York"), 10);
}
}