use std::borrow::Cow;
use crate::codec::primitives::{Reader, Writer};
use crate::error::{DecodeError, EncodeError};
use crate::limits::{MAX_BYTES_LEN, MAX_EMBEDDING_BYTES, MAX_EMBEDDING_DIMS, MAX_POSITION_LEN, MAX_STRING_LEN};
use crate::model::{
DataType, DecimalMantissa, DictionaryBuilder, EmbeddingSubType, PropertyValue, Value,
WireDictionaries,
};
use crate::util::{
format_date_rfc3339, format_datetime_rfc3339, format_time_rfc3339,
parse_date_rfc3339, parse_datetime_rfc3339, parse_time_rfc3339,
};
pub fn decode_value<'a>(
reader: &mut Reader<'a>,
data_type: DataType,
dicts: &WireDictionaries,
) -> Result<Value<'a>, DecodeError> {
match data_type {
DataType::Boolean => decode_boolean(reader),
DataType::Integer => decode_integer(reader, dicts),
DataType::Float => decode_float(reader, dicts),
DataType::Decimal => decode_decimal(reader, dicts),
DataType::Text => decode_text(reader, dicts),
DataType::Bytes => decode_bytes(reader),
DataType::Date => decode_date(reader),
DataType::Time => decode_time(reader),
DataType::Datetime => decode_datetime(reader),
DataType::Schedule => decode_schedule(reader),
DataType::Point => decode_point(reader),
DataType::Rect => decode_rect(reader),
DataType::Embedding => decode_embedding(reader),
}
}
fn decode_boolean<'a>(reader: &mut Reader<'a>) -> Result<Value<'a>, DecodeError> {
let byte = reader.read_byte("boolean")?;
match byte {
0x00 => Ok(Value::Boolean(false)),
0x01 => Ok(Value::Boolean(true)),
_ => Err(DecodeError::InvalidBoolean { value: byte }),
}
}
fn decode_integer<'a>(reader: &mut Reader<'a>, dicts: &WireDictionaries) -> Result<Value<'a>, DecodeError> {
let value = reader.read_signed_varint("integer")?;
let unit_index = reader.read_varint("integer.unit")? as usize;
let unit = if unit_index == 0 {
None
} else {
let idx = unit_index - 1;
if idx >= dicts.units.len() {
return Err(DecodeError::IndexOutOfBounds {
dict: "units",
index: unit_index,
size: dicts.units.len() + 1,
});
}
Some(dicts.units[idx])
};
Ok(Value::Integer { value, unit })
}
fn decode_float<'a>(reader: &mut Reader<'a>, dicts: &WireDictionaries) -> Result<Value<'a>, DecodeError> {
let value = reader.read_f64("float")?;
let unit_index = reader.read_varint("float.unit")? as usize;
let unit = if unit_index == 0 {
None
} else {
let idx = unit_index - 1;
if idx >= dicts.units.len() {
return Err(DecodeError::IndexOutOfBounds {
dict: "units",
index: unit_index,
size: dicts.units.len() + 1,
});
}
Some(dicts.units[idx])
};
Ok(Value::Float { value, unit })
}
fn decode_decimal<'a>(reader: &mut Reader<'a>, dicts: &WireDictionaries) -> Result<Value<'a>, DecodeError> {
let exponent = reader.read_signed_varint("decimal.exponent")? as i32;
let mantissa_type = reader.read_byte("decimal.mantissa_type")?;
let mantissa = match mantissa_type {
0x00 => {
let v = reader.read_signed_varint("decimal.mantissa")?;
DecimalMantissa::I64(v)
}
0x01 => {
let len = reader.read_varint("decimal.mantissa_len")? as usize;
let bytes = reader.read_bytes(len, "decimal.mantissa_bytes")?;
if !bytes.is_empty() {
let first = bytes[0];
if bytes.len() > 1 {
let second = bytes[1];
if (first == 0x00 && (second & 0x80) == 0)
|| (first == 0xFF && (second & 0x80) != 0) {
return Err(DecodeError::DecimalMantissaNotMinimal);
}
}
}
DecimalMantissa::Big(Cow::Borrowed(bytes))
}
_ => {
return Err(DecodeError::MalformedEncoding {
context: "invalid decimal mantissa type"
});
}
};
match &mantissa {
DecimalMantissa::I64(v) => {
if *v == 0 {
if exponent != 0 {
return Err(DecodeError::DecimalNotNormalized);
}
} else if *v % 10 == 0 {
return Err(DecodeError::DecimalNotNormalized);
}
}
DecimalMantissa::Big(bytes) => {
if is_big_mantissa_zero(bytes) {
if exponent != 0 {
return Err(DecodeError::DecimalNotNormalized);
}
} else if is_big_mantissa_divisible_by_10(bytes) {
return Err(DecodeError::DecimalNotNormalized);
}
}
}
let unit_index = reader.read_varint("decimal.unit")? as usize;
let unit = if unit_index == 0 {
None
} else {
let idx = unit_index - 1;
if idx >= dicts.units.len() {
return Err(DecodeError::IndexOutOfBounds {
dict: "units",
index: unit_index,
size: dicts.units.len() + 1,
});
}
Some(dicts.units[idx])
};
Ok(Value::Decimal { exponent, mantissa, unit })
}
fn is_big_mantissa_zero(bytes: &[u8]) -> bool {
bytes.iter().all(|&b| b == 0)
}
fn is_big_mantissa_divisible_by_10(bytes: &[u8]) -> bool {
if bytes.is_empty() {
return true; }
let is_negative = bytes[0] & 0x80 != 0;
if is_negative {
let abs_mod = twos_complement_abs_mod_10(bytes);
abs_mod == 0
} else {
let mut remainder = 0u32;
for &byte in bytes {
remainder = (remainder * 6 + byte as u32) % 10;
}
remainder == 0
}
}
fn twos_complement_abs_mod_10(bytes: &[u8]) -> u32 {
let mut remainder = 0u32;
for &byte in bytes {
let inverted = !byte;
remainder = (remainder * 6 + inverted as u32) % 10;
}
(remainder + 1) % 10
}
fn decode_text<'a>(reader: &mut Reader<'a>, dicts: &WireDictionaries) -> Result<Value<'a>, DecodeError> {
let value = reader.read_str(MAX_STRING_LEN, "text")?;
let lang_index = reader.read_varint("text.language")? as usize;
let language = if lang_index == 0 {
None
} else {
let idx = lang_index - 1;
if idx >= dicts.languages.len() {
return Err(DecodeError::IndexOutOfBounds {
dict: "languages",
index: lang_index,
size: dicts.languages.len() + 1, });
}
Some(dicts.languages[idx])
};
Ok(Value::Text { value: Cow::Borrowed(value), language })
}
fn decode_bytes<'a>(reader: &mut Reader<'a>) -> Result<Value<'a>, DecodeError> {
let len = reader.read_varint("bytes.len")? as usize;
if len > MAX_BYTES_LEN {
return Err(DecodeError::LengthExceedsLimit {
field: "bytes",
len,
max: MAX_BYTES_LEN,
});
}
let bytes = reader.read_bytes(len, "bytes")?;
Ok(Value::Bytes(Cow::Borrowed(bytes)))
}
fn decode_date<'a>(reader: &mut Reader<'a>) -> Result<Value<'a>, DecodeError> {
let bytes = reader.read_bytes(6, "date")?;
let days = i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let offset_min = i16::from_le_bytes([bytes[4], bytes[5]]);
if offset_min < -1440 || offset_min > 1440 {
return Err(DecodeError::MalformedEncoding {
context: "DATE offset_min outside range [-1440, +1440]",
});
}
let value = format_date_rfc3339(days, offset_min);
Ok(Value::Date(Cow::Owned(value)))
}
fn decode_time<'a>(reader: &mut Reader<'a>) -> Result<Value<'a>, DecodeError> {
let bytes = reader.read_bytes(8, "time")?;
let time_micros_unsigned = u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], 0, 0
]);
let time_micros = if time_micros_unsigned & 0x8000_0000_0000 != 0 {
(time_micros_unsigned | 0xFFFF_0000_0000_0000) as i64
} else {
time_micros_unsigned as i64
};
let offset_min = i16::from_le_bytes([bytes[6], bytes[7]]);
if time_micros < 0 || time_micros > 86_399_999_999 {
return Err(DecodeError::MalformedEncoding {
context: "TIME time_micros outside range [0, 86399999999]",
});
}
if offset_min < -1440 || offset_min > 1440 {
return Err(DecodeError::MalformedEncoding {
context: "TIME offset_min outside range [-1440, +1440]",
});
}
let value = format_time_rfc3339(time_micros, offset_min);
Ok(Value::Time(Cow::Owned(value)))
}
fn decode_datetime<'a>(reader: &mut Reader<'a>) -> Result<Value<'a>, DecodeError> {
let bytes = reader.read_bytes(10, "datetime")?;
let epoch_micros = i64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3],
bytes[4], bytes[5], bytes[6], bytes[7]
]);
let offset_min = i16::from_le_bytes([bytes[8], bytes[9]]);
if offset_min < -1440 || offset_min > 1440 {
return Err(DecodeError::MalformedEncoding {
context: "DATETIME offset_min outside range [-1440, +1440]",
});
}
let value = format_datetime_rfc3339(epoch_micros, offset_min);
Ok(Value::Datetime(Cow::Owned(value)))
}
fn decode_schedule<'a>(reader: &mut Reader<'a>) -> Result<Value<'a>, DecodeError> {
let value = reader.read_str(MAX_STRING_LEN, "schedule")?;
Ok(Value::Schedule(Cow::Borrowed(value)))
}
fn decode_point<'a>(reader: &mut Reader<'a>) -> Result<Value<'a>, DecodeError> {
let ordinate_count = reader.read_byte("point.ordinate_count")?;
if ordinate_count != 2 && ordinate_count != 3 {
return Err(DecodeError::MalformedEncoding {
context: "POINT ordinate_count must be 2 or 3",
});
}
let lat = reader.read_f64("point.lat")?;
let lon = reader.read_f64("point.lon")?;
let alt = if ordinate_count == 3 {
Some(reader.read_f64("point.alt")?)
} else {
None
};
if !(-90.0..=90.0).contains(&lat) {
return Err(DecodeError::LatitudeOutOfRange { lat });
}
if !(-180.0..=180.0).contains(&lon) {
return Err(DecodeError::LongitudeOutOfRange { lon });
}
if lat.is_nan() || lon.is_nan() {
return Err(DecodeError::FloatIsNan);
}
if let Some(a) = alt {
if a.is_nan() {
return Err(DecodeError::FloatIsNan);
}
}
Ok(Value::Point { lat, lon, alt })
}
fn decode_rect<'a>(reader: &mut Reader<'a>) -> Result<Value<'a>, DecodeError> {
let min_lat = reader.read_f64("rect.min_lat")?;
let min_lon = reader.read_f64("rect.min_lon")?;
let max_lat = reader.read_f64("rect.max_lat")?;
let max_lon = reader.read_f64("rect.max_lon")?;
if !(-90.0..=90.0).contains(&min_lat) || !(-90.0..=90.0).contains(&max_lat) {
return Err(DecodeError::LatitudeOutOfRange { lat: if !(-90.0..=90.0).contains(&min_lat) { min_lat } else { max_lat } });
}
if !(-180.0..=180.0).contains(&min_lon) || !(-180.0..=180.0).contains(&max_lon) {
return Err(DecodeError::LongitudeOutOfRange { lon: if !(-180.0..=180.0).contains(&min_lon) { min_lon } else { max_lon } });
}
if min_lat.is_nan() || min_lon.is_nan() || max_lat.is_nan() || max_lon.is_nan() {
return Err(DecodeError::FloatIsNan);
}
Ok(Value::Rect { min_lat, min_lon, max_lat, max_lon })
}
fn decode_embedding<'a>(reader: &mut Reader<'a>) -> Result<Value<'a>, DecodeError> {
let sub_type_byte = reader.read_byte("embedding.sub_type")?;
let sub_type = EmbeddingSubType::from_u8(sub_type_byte)
.ok_or(DecodeError::InvalidEmbeddingSubType { sub_type: sub_type_byte })?;
let dims = reader.read_varint("embedding.dims")? as usize;
if dims > MAX_EMBEDDING_DIMS {
return Err(DecodeError::LengthExceedsLimit {
field: "embedding.dims",
len: dims,
max: MAX_EMBEDDING_DIMS,
});
}
let expected_bytes = sub_type.bytes_for_dims(dims);
if expected_bytes > MAX_EMBEDDING_BYTES {
return Err(DecodeError::LengthExceedsLimit {
field: "embedding.data",
len: expected_bytes,
max: MAX_EMBEDDING_BYTES,
});
}
let data = reader.read_bytes(expected_bytes, "embedding.data")?;
if sub_type == EmbeddingSubType::Float32 {
for chunk in data.chunks_exact(4) {
let f = f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
if f.is_nan() {
return Err(DecodeError::FloatIsNan);
}
}
}
if sub_type == EmbeddingSubType::Binary && dims % 8 != 0 {
let last_byte = data[data.len() - 1];
let unused_bits = 8 - (dims % 8);
let mask = !((1u8 << (8 - unused_bits)) - 1);
if last_byte & mask != 0 {
return Err(DecodeError::MalformedEncoding {
context: "binary embedding has non-zero unused bits",
});
}
}
Ok(Value::Embedding { sub_type, dims, data: Cow::Borrowed(data) })
}
pub fn decode_property_value<'a>(
reader: &mut Reader<'a>,
dicts: &WireDictionaries,
) -> Result<PropertyValue<'a>, DecodeError> {
let prop_index = reader.read_varint("property")? as usize;
if prop_index >= dicts.properties.len() {
return Err(DecodeError::IndexOutOfBounds {
dict: "properties",
index: prop_index,
size: dicts.properties.len(),
});
}
let (property, data_type) = dicts.properties[prop_index];
let value = decode_value(reader, data_type, dicts)?;
Ok(PropertyValue { property, value })
}
pub fn encode_value(
writer: &mut Writer,
value: &Value<'_>,
dict_builder: &mut DictionaryBuilder,
) -> Result<(), EncodeError> {
match value {
Value::Boolean(v) => {
writer.write_byte(if *v { 0x01 } else { 0x00 });
}
Value::Integer { value, unit } => {
writer.write_signed_varint(*value);
let unit_index = dict_builder.add_unit(*unit);
writer.write_varint(unit_index as u64);
}
Value::Float { value, unit } => {
if value.is_nan() {
return Err(EncodeError::FloatIsNan);
}
writer.write_f64(*value);
let unit_index = dict_builder.add_unit(*unit);
writer.write_varint(unit_index as u64);
}
Value::Decimal { exponent, mantissa, unit } => {
encode_decimal(writer, *exponent, mantissa)?;
let unit_index = dict_builder.add_unit(*unit);
writer.write_varint(unit_index as u64);
}
Value::Text { value, language } => {
writer.write_string(value);
let lang_index = dict_builder.add_language(*language);
writer.write_varint(lang_index as u64);
}
Value::Bytes(bytes) => {
writer.write_bytes_prefixed(bytes);
}
Value::Date(s) => {
let (days, offset_min) = parse_date_rfc3339(s).map_err(|_| EncodeError::InvalidInput {
context: "Invalid RFC 3339 date format",
})?;
writer.write_bytes(&days.to_le_bytes());
writer.write_bytes(&offset_min.to_le_bytes());
}
Value::Time(s) => {
let (time_micros, offset_min) = parse_time_rfc3339(s).map_err(|_| EncodeError::InvalidInput {
context: "Invalid RFC 3339 time format",
})?;
if time_micros < 0 || time_micros > 86_399_999_999 {
return Err(EncodeError::InvalidInput {
context: "TIME time_micros outside range [0, 86399999999]",
});
}
let time_bytes = time_micros.to_le_bytes();
writer.write_bytes(&time_bytes[0..6]);
writer.write_bytes(&offset_min.to_le_bytes());
}
Value::Datetime(s) => {
let (epoch_micros, offset_min) = parse_datetime_rfc3339(s).map_err(|_| EncodeError::InvalidInput {
context: "Invalid RFC 3339 datetime format",
})?;
writer.write_bytes(&epoch_micros.to_le_bytes());
writer.write_bytes(&offset_min.to_le_bytes());
}
Value::Schedule(s) => {
writer.write_string(s);
}
Value::Point { lat, lon, alt } => {
if *lat < -90.0 || *lat > 90.0 {
return Err(EncodeError::LatitudeOutOfRange { lat: *lat });
}
if *lon < -180.0 || *lon > 180.0 {
return Err(EncodeError::LongitudeOutOfRange { lon: *lon });
}
if lat.is_nan() || lon.is_nan() {
return Err(EncodeError::FloatIsNan);
}
if let Some(a) = alt {
if a.is_nan() {
return Err(EncodeError::FloatIsNan);
}
}
let ordinate_count = if alt.is_some() { 3u8 } else { 2u8 };
writer.write_byte(ordinate_count);
writer.write_f64(*lat);
writer.write_f64(*lon);
if let Some(a) = alt {
writer.write_f64(*a);
}
}
Value::Rect { min_lat, min_lon, max_lat, max_lon } => {
if *min_lat < -90.0 || *min_lat > 90.0 || *max_lat < -90.0 || *max_lat > 90.0 {
return Err(EncodeError::LatitudeOutOfRange { lat: if *min_lat < -90.0 || *min_lat > 90.0 { *min_lat } else { *max_lat } });
}
if *min_lon < -180.0 || *min_lon > 180.0 || *max_lon < -180.0 || *max_lon > 180.0 {
return Err(EncodeError::LongitudeOutOfRange { lon: if *min_lon < -180.0 || *min_lon > 180.0 { *min_lon } else { *max_lon } });
}
if min_lat.is_nan() || min_lon.is_nan() || max_lat.is_nan() || max_lon.is_nan() {
return Err(EncodeError::FloatIsNan);
}
writer.write_f64(*min_lat);
writer.write_f64(*min_lon);
writer.write_f64(*max_lat);
writer.write_f64(*max_lon);
}
Value::Embedding { sub_type, dims, data } => {
let expected = sub_type.bytes_for_dims(*dims);
if data.len() != expected {
return Err(EncodeError::EmbeddingDimensionMismatch {
sub_type: *sub_type as u8,
dims: *dims,
data_len: data.len(),
});
}
if *sub_type == EmbeddingSubType::Float32 {
for chunk in data.chunks_exact(4) {
let f = f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
if f.is_nan() {
return Err(EncodeError::FloatIsNan);
}
}
}
writer.write_byte(*sub_type as u8);
writer.write_varint(*dims as u64);
writer.write_bytes(data);
}
}
Ok(())
}
fn encode_decimal(
writer: &mut Writer,
exponent: i32,
mantissa: &DecimalMantissa<'_>,
) -> Result<(), EncodeError> {
match mantissa {
DecimalMantissa::I64(v) => {
if *v == 0 {
if exponent != 0 {
return Err(EncodeError::DecimalNotNormalized);
}
} else if *v % 10 == 0 {
return Err(EncodeError::DecimalNotNormalized);
}
}
DecimalMantissa::Big(bytes) => {
if is_big_mantissa_zero(bytes) {
if exponent != 0 {
return Err(EncodeError::DecimalNotNormalized);
}
} else if is_big_mantissa_divisible_by_10(bytes) {
return Err(EncodeError::DecimalNotNormalized);
}
}
}
writer.write_signed_varint(exponent as i64);
match mantissa {
DecimalMantissa::I64(v) => {
writer.write_byte(0x00);
writer.write_signed_varint(*v);
}
DecimalMantissa::Big(bytes) => {
writer.write_byte(0x01);
writer.write_varint(bytes.len() as u64);
writer.write_bytes(bytes);
}
}
Ok(())
}
pub fn encode_property_value(
writer: &mut Writer,
pv: &PropertyValue<'_>,
dict_builder: &mut DictionaryBuilder,
data_type: DataType,
) -> Result<(), EncodeError> {
let prop_index = dict_builder.add_property(pv.property, data_type);
writer.write_varint(prop_index as u64);
encode_value(writer, &pv.value, dict_builder)?;
Ok(())
}
pub fn validate_position(pos: &str) -> Result<(), EncodeError> {
if pos.len() > MAX_POSITION_LEN {
return Err(EncodeError::PositionTooLong);
}
for c in pos.chars() {
if !c.is_ascii_alphanumeric() {
return Err(EncodeError::InvalidPositionChar);
}
}
Ok(())
}
pub fn decode_position<'a>(reader: &mut Reader<'a>) -> Result<Cow<'a, str>, DecodeError> {
let pos = reader.read_str(MAX_POSITION_LEN, "position")?;
for c in pos.chars() {
if !c.is_ascii_alphanumeric() {
return Err(DecodeError::InvalidPositionChar { char: c });
}
}
Ok(Cow::Borrowed(pos))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_boolean_roundtrip() {
for v in [true, false] {
let value = Value::Boolean(v);
let dicts = WireDictionaries::default();
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
encode_value(&mut writer, &value, &mut dict_builder).unwrap();
let mut reader = Reader::new(writer.as_bytes());
let decoded = decode_value(&mut reader, DataType::Boolean, &dicts).unwrap();
assert_eq!(value, decoded);
}
}
#[test]
fn test_integer_roundtrip() {
for v in [0i64, 1, -1, i64::MAX, i64::MIN, 12345678] {
let value = Value::Integer { value: v, unit: None };
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
encode_value(&mut writer, &value, &mut dict_builder).unwrap();
let dicts = dict_builder.build();
let mut reader = Reader::new(writer.as_bytes());
let decoded = decode_value(&mut reader, DataType::Integer, &dicts).unwrap();
assert_eq!(value, decoded);
}
}
#[test]
fn test_float_roundtrip() {
for v in [0.0, 1.0, -1.0, f64::INFINITY, f64::NEG_INFINITY, 3.14159] {
let value = Value::Float { value: v, unit: None };
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
encode_value(&mut writer, &value, &mut dict_builder).unwrap();
let dicts = dict_builder.build();
let mut reader = Reader::new(writer.as_bytes());
let decoded = decode_value(&mut reader, DataType::Float, &dicts).unwrap();
assert_eq!(value, decoded);
}
}
#[test]
fn test_text_roundtrip() {
let value = Value::Text {
value: Cow::Owned("hello world".to_string()),
language: None,
};
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
encode_value(&mut writer, &value, &mut dict_builder).unwrap();
let decode_dicts = dict_builder.build();
let mut reader = Reader::new(writer.as_bytes());
let decoded = decode_value(&mut reader, DataType::Text, &decode_dicts).unwrap();
match (&value, &decoded) {
(Value::Text { value: v1, language: l1 }, Value::Text { value: v2, language: l2 }) => {
assert_eq!(v1.as_ref(), v2.as_ref());
assert_eq!(l1, l2);
}
_ => panic!("expected Text values"),
}
}
#[test]
fn test_point_roundtrip() {
let value = Value::Point { lat: 37.7749, lon: -122.4194, alt: None };
let dicts = WireDictionaries::default();
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
encode_value(&mut writer, &value, &mut dict_builder).unwrap();
let mut reader = Reader::new(writer.as_bytes());
let decoded = decode_value(&mut reader, DataType::Point, &dicts).unwrap();
assert_eq!(value, decoded);
let value_3d = Value::Point { lat: 37.7749, lon: -122.4194, alt: Some(100.0) };
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
encode_value(&mut writer, &value_3d, &mut dict_builder).unwrap();
let mut reader = Reader::new(writer.as_bytes());
let decoded_3d = decode_value(&mut reader, DataType::Point, &dicts).unwrap();
assert_eq!(value_3d, decoded_3d);
}
#[test]
fn test_point_validation() {
let value = Value::Point { lat: 91.0, lon: 0.0, alt: None };
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
let result = encode_value(&mut writer, &value, &mut dict_builder);
assert!(result.is_err());
let value = Value::Point { lat: 0.0, lon: 181.0, alt: None };
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
let result = encode_value(&mut writer, &value, &mut dict_builder);
assert!(result.is_err());
let value = Value::Point { lat: 0.0, lon: 0.0, alt: Some(f64::NAN) };
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
let result = encode_value(&mut writer, &value, &mut dict_builder);
assert!(result.is_err());
}
#[test]
fn test_rect_roundtrip() {
let value = Value::Rect {
min_lat: 24.5,
min_lon: -125.0,
max_lat: 49.4,
max_lon: -66.9,
};
let dicts = WireDictionaries::default();
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
encode_value(&mut writer, &value, &mut dict_builder).unwrap();
let mut reader = Reader::new(writer.as_bytes());
let decoded = decode_value(&mut reader, DataType::Rect, &dicts).unwrap();
assert_eq!(value, decoded);
}
#[test]
fn test_rect_validation() {
let value = Value::Rect { min_lat: -91.0, min_lon: 0.0, max_lat: 0.0, max_lon: 0.0 };
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
let result = encode_value(&mut writer, &value, &mut dict_builder);
assert!(result.is_err());
let value = Value::Rect { min_lat: 0.0, min_lon: 0.0, max_lat: 91.0, max_lon: 0.0 };
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
let result = encode_value(&mut writer, &value, &mut dict_builder);
assert!(result.is_err());
let value = Value::Rect { min_lat: 0.0, min_lon: -181.0, max_lat: 0.0, max_lon: 0.0 };
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
let result = encode_value(&mut writer, &value, &mut dict_builder);
assert!(result.is_err());
let value = Value::Rect { min_lat: 0.0, min_lon: 0.0, max_lat: 0.0, max_lon: 181.0 };
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
let result = encode_value(&mut writer, &value, &mut dict_builder);
assert!(result.is_err());
let value = Value::Rect { min_lat: f64::NAN, min_lon: 0.0, max_lat: 0.0, max_lon: 0.0 };
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
let result = encode_value(&mut writer, &value, &mut dict_builder);
assert!(result.is_err());
}
#[test]
fn test_schedule_roundtrip() {
let dicts = WireDictionaries::default();
let mut dict_builder = DictionaryBuilder::new();
let value = Value::Schedule(Cow::Owned("BEGIN:VEVENT\r\nDTSTART:20240315T090000Z\r\nDTEND:20240315T100000Z\r\nEND:VEVENT".to_string()));
let mut writer = Writer::new();
encode_value(&mut writer, &value, &mut dict_builder).unwrap();
let mut reader = Reader::new(writer.as_bytes());
let decoded = decode_value(&mut reader, DataType::Schedule, &dicts).unwrap();
match (&value, &decoded) {
(Value::Schedule(s1), Value::Schedule(s2)) => {
assert_eq!(s1.as_ref(), s2.as_ref());
}
_ => panic!("expected Schedule values"),
}
}
#[test]
fn test_embedding_roundtrip() {
let value = Value::Embedding {
sub_type: EmbeddingSubType::Float32,
dims: 4,
data: Cow::Owned(vec![0u8; 16]), };
let dicts = WireDictionaries::default();
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
encode_value(&mut writer, &value, &mut dict_builder).unwrap();
let mut reader = Reader::new(writer.as_bytes());
let decoded = decode_value(&mut reader, DataType::Embedding, &dicts).unwrap();
match (&value, &decoded) {
(
Value::Embedding { sub_type: s1, dims: d1, data: data1 },
Value::Embedding { sub_type: s2, dims: d2, data: data2 },
) => {
assert_eq!(s1, s2);
assert_eq!(d1, d2);
assert_eq!(data1.as_ref(), data2.as_ref());
}
_ => panic!("expected Embedding values"),
}
}
#[test]
fn test_decimal_normalized() {
let valid = Value::Decimal {
exponent: -2,
mantissa: DecimalMantissa::I64(1234),
unit: None,
};
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &valid, &mut dict_builder).is_ok());
let invalid = Value::Decimal {
exponent: -2,
mantissa: DecimalMantissa::I64(1230),
unit: None,
};
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &invalid, &mut dict_builder).is_err());
}
#[test]
fn test_date_roundtrip() {
let dicts = WireDictionaries::default();
let mut dict_builder = DictionaryBuilder::new();
let test_cases = [
"1970-01-01Z", "2024-03-15Z", "2024-03-15+05:30", "2024-03-15-08:00", ];
for date_str in test_cases {
let value = Value::Date(Cow::Owned(date_str.to_string()));
let mut writer = Writer::new();
encode_value(&mut writer, &value, &mut dict_builder).unwrap();
let mut reader = Reader::new(writer.as_bytes());
let decoded = decode_value(&mut reader, DataType::Date, &dicts).unwrap();
match (&value, &decoded) {
(Value::Date(v1), Value::Date(v2)) => {
assert_eq!(v1.as_ref(), v2.as_ref(), "Roundtrip failed for {}", date_str);
}
_ => panic!("expected Date values"),
}
}
}
#[test]
fn test_time_roundtrip() {
let dicts = WireDictionaries::default();
let mut dict_builder = DictionaryBuilder::new();
let test_cases = [
"00:00:00Z", "14:30:00Z", "14:30:00.5+05:30", "23:59:59.999999Z", "00:00:00-05:00", ];
for time_str in test_cases {
let value = Value::Time(Cow::Owned(time_str.to_string()));
let mut writer = Writer::new();
encode_value(&mut writer, &value, &mut dict_builder).unwrap();
let mut reader = Reader::new(writer.as_bytes());
let decoded = decode_value(&mut reader, DataType::Time, &dicts).unwrap();
match (&value, &decoded) {
(Value::Time(v1), Value::Time(v2)) => {
assert_eq!(v1.as_ref(), v2.as_ref(), "Roundtrip failed for {}", time_str);
}
_ => panic!("expected Time values"),
}
}
}
#[test]
fn test_datetime_roundtrip() {
let dicts = WireDictionaries::default();
let mut dict_builder = DictionaryBuilder::new();
let test_cases = [
"1970-01-01T00:00:00Z", "2024-03-15T14:30:00Z", "2024-03-15T14:30:00+05:30", "2024-03-15T14:30:00.123456Z", ];
for datetime_str in test_cases {
let value = Value::Datetime(Cow::Owned(datetime_str.to_string()));
let mut writer = Writer::new();
encode_value(&mut writer, &value, &mut dict_builder).unwrap();
let mut reader = Reader::new(writer.as_bytes());
let decoded = decode_value(&mut reader, DataType::Datetime, &dicts).unwrap();
match (&value, &decoded) {
(Value::Datetime(v1), Value::Datetime(v2)) => {
assert_eq!(v1.as_ref(), v2.as_ref(), "Roundtrip failed for {}", datetime_str);
}
_ => panic!("expected Datetime values"),
}
}
}
#[test]
fn test_date_validation() {
let mut dict_builder = DictionaryBuilder::new();
let invalid = Value::Date(Cow::Borrowed("not-a-date"));
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &invalid, &mut dict_builder).is_err());
let invalid_month = Value::Date(Cow::Borrowed("2024-13-01"));
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &invalid_month, &mut dict_builder).is_err());
}
#[test]
fn test_time_validation() {
let mut dict_builder = DictionaryBuilder::new();
let invalid = Value::Time(Cow::Borrowed("not:a:time"));
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &invalid, &mut dict_builder).is_err());
let invalid_hours = Value::Time(Cow::Borrowed("24:00:00"));
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &invalid_hours, &mut dict_builder).is_err());
let invalid_minutes = Value::Time(Cow::Borrowed("14:60:00"));
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &invalid_minutes, &mut dict_builder).is_err());
}
#[test]
fn test_datetime_validation() {
let mut dict_builder = DictionaryBuilder::new();
let invalid = Value::Datetime(Cow::Borrowed("not-a-datetime"));
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &invalid, &mut dict_builder).is_err());
let invalid_date = Value::Datetime(Cow::Borrowed("2024-13-01T00:00:00Z"));
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &invalid_date, &mut dict_builder).is_err());
let invalid_time = Value::Datetime(Cow::Borrowed("2024-01-01T25:00:00Z"));
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &invalid_time, &mut dict_builder).is_err());
}
#[test]
fn test_big_decimal_normalization_helpers() {
assert!(is_big_mantissa_zero(&[]));
assert!(is_big_mantissa_zero(&[0]));
assert!(is_big_mantissa_zero(&[0, 0, 0]));
assert!(!is_big_mantissa_zero(&[1]));
assert!(!is_big_mantissa_zero(&[0, 1]));
assert!(is_big_mantissa_divisible_by_10(&[0x0A])); assert!(is_big_mantissa_divisible_by_10(&[0x14])); assert!(is_big_mantissa_divisible_by_10(&[0x64])); assert!(is_big_mantissa_divisible_by_10(&[0x01, 0xF4]));
assert!(!is_big_mantissa_divisible_by_10(&[0x01])); assert!(!is_big_mantissa_divisible_by_10(&[0x07])); assert!(!is_big_mantissa_divisible_by_10(&[0x0B])); assert!(!is_big_mantissa_divisible_by_10(&[0x15]));
assert!(is_big_mantissa_divisible_by_10(&[0xF6])); assert!(is_big_mantissa_divisible_by_10(&[0xEC])); assert!(!is_big_mantissa_divisible_by_10(&[0xFF])); assert!(!is_big_mantissa_divisible_by_10(&[0xF9])); }
#[test]
fn test_big_decimal_normalization_encode() {
let valid = Value::Decimal {
exponent: 0,
mantissa: DecimalMantissa::Big(Cow::Owned(vec![0x07])), unit: None,
};
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &valid, &mut dict_builder).is_ok());
let invalid = Value::Decimal {
exponent: 0,
mantissa: DecimalMantissa::Big(Cow::Owned(vec![0x0A])), unit: None,
};
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &invalid, &mut dict_builder).is_err());
let invalid_zero = Value::Decimal {
exponent: 1,
mantissa: DecimalMantissa::Big(Cow::Owned(vec![0x00])),
unit: None,
};
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &invalid_zero, &mut dict_builder).is_err());
let valid_zero = Value::Decimal {
exponent: 0,
mantissa: DecimalMantissa::Big(Cow::Owned(vec![0x00])),
unit: None,
};
let mut dict_builder = DictionaryBuilder::new();
let mut writer = Writer::new();
assert!(encode_value(&mut writer, &valid_zero, &mut dict_builder).is_ok());
}
}