use std::fmt;
use crate::{
CanonicalBytes, CanonicalList, CanonicalRecord, CanonicalString, CanonicalValue,
CanonicalVector, CurrencyCode, Date, Decimal, DecimalSpec, EnumTypeId, EnumVariantId, FieldId,
Money, Timestamp,
limits::{
MAX_ATOMIC_COMMAND_REQUEST_BYTES_V2, MAX_BYTES_VALUE_BYTES, MAX_CANONICAL_DOCUMENT_BYTES,
MAX_LIST_ENTRIES, MAX_NESTING_DEPTH, MAX_RECORD_FIELDS, MAX_STRING_BYTES,
},
};
#[path = "codec_decode_capacity.rs"]
mod decode_capacity;
pub use decode_capacity::*;
pub const CANONICAL_VALUE_VERSION: u8 = 0x01;
const TAG_NULL: u8 = 0x00;
const TAG_BOOL: u8 = 0x01;
const TAG_I64: u8 = 0x02;
const TAG_U64: u8 = 0x03;
const TAG_DECIMAL: u8 = 0x04;
const TAG_MONEY: u8 = 0x05;
const TAG_STRING: u8 = 0x06;
const TAG_BYTES: u8 = 0x07;
const TAG_TIMESTAMP: u8 = 0x08;
const TAG_DATE: u8 = 0x09;
const TAG_UUID: u8 = 0x0a;
const TAG_ENUM: u8 = 0x0b;
const TAG_LIST: u8 = 0x0c;
const TAG_RECORD: u8 = 0x0d;
const TAG_VECTOR: u8 = 0x0e;
pub fn encode_canonical_value(value: &CanonicalValue) -> Result<Vec<u8>, CanonicalCodecError> {
let mut output = Vec::with_capacity(reserve_hint(canonical_value_encoded_len(value)));
encode_canonical_value_into(&mut output, value)?;
Ok(output)
}
const fn reserve_hint(length: Result<usize, CanonicalCodecError>) -> usize {
match length {
Ok(length) => length,
Err(_) => 0,
}
}
pub fn encode_canonical_value_into(
output: &mut Vec<u8>,
value: &CanonicalValue,
) -> Result<(), CanonicalCodecError> {
let document_start = output.len();
let mut encoder = Encoder {
output: std::mem::take(output),
document_start,
maximum_document_bytes: MAX_CANONICAL_DOCUMENT_BYTES,
};
let result = encoder.encode_value(value, 0);
*output = encoder.output;
if result.is_err() {
output.truncate(document_start);
}
result
}
pub fn canonical_value_encoded_len(value: &CanonicalValue) -> Result<usize, CanonicalCodecError> {
let mut counter = LengthCounter::new(MAX_CANONICAL_DOCUMENT_BYTES);
counter.encode_value(value, 0)?;
Ok(counter.len)
}
pub fn canonical_record_encoded_len(
record: &CanonicalRecord,
) -> Result<usize, CanonicalCodecError> {
let mut counter = LengthCounter::new(MAX_CANONICAL_DOCUMENT_BYTES);
counter.encode_record_value(record, 0)?;
Ok(counter.len)
}
pub fn encode_canonical_record(record: &CanonicalRecord) -> Result<Vec<u8>, CanonicalCodecError> {
let mut encoder = Encoder {
output: Vec::with_capacity(reserve_hint(canonical_record_encoded_len(record))),
document_start: 0,
maximum_document_bytes: MAX_CANONICAL_DOCUMENT_BYTES,
};
encoder.encode_record_value(record, 0)?;
Ok(encoder.output)
}
pub fn encode_canonical_record_into(
output: &mut Vec<u8>,
record: &CanonicalRecord,
) -> Result<(), CanonicalCodecError> {
let document_start = output.len();
let mut encoder = Encoder {
output: std::mem::take(output),
document_start,
maximum_document_bytes: MAX_CANONICAL_DOCUMENT_BYTES,
};
let result = encoder.encode_record_value(record, 0);
*output = encoder.output;
if result.is_err() {
output.truncate(document_start);
}
result
}
pub fn canonical_command_input_encoded_len(
record: &CanonicalRecord,
) -> Result<usize, CanonicalCodecError> {
let mut counter = LengthCounter::new(MAX_ATOMIC_COMMAND_REQUEST_BYTES_V2);
counter.encode_record_value(record, 0)?;
Ok(counter.len)
}
pub fn encode_canonical_command_input(
record: &CanonicalRecord,
) -> Result<Vec<u8>, CanonicalCodecError> {
let mut encoder = Encoder {
output: Vec::with_capacity(reserve_hint(canonical_command_input_encoded_len(record))),
document_start: 0,
maximum_document_bytes: MAX_ATOMIC_COMMAND_REQUEST_BYTES_V2,
};
encoder.encode_record_value(record, 0)?;
Ok(encoder.output)
}
pub fn decode_canonical_value(input: &[u8]) -> Result<CanonicalValue, CanonicalCodecError> {
if input.len() > MAX_CANONICAL_DOCUMENT_BYTES {
return Err(CanonicalCodecError::DocumentTooLarge {
actual: input.len(),
maximum: MAX_CANONICAL_DOCUMENT_BYTES,
});
}
let mut decoder = Decoder { input, position: 0 };
let value = decoder.decode_value(0)?;
if decoder.position != input.len() {
return Err(CanonicalCodecError::TrailingBytes {
count: input.len() - decoder.position,
});
}
Ok(value)
}
struct Encoder {
output: Vec<u8>,
document_start: usize,
maximum_document_bytes: usize,
}
struct LengthCounter {
len: usize,
maximum_document_bytes: usize,
}
impl LengthCounter {
const fn new(maximum_document_bytes: usize) -> Self {
Self {
len: 0,
maximum_document_bytes,
}
}
}
impl Encoder {
fn validate_depth(depth: usize) -> Result<(), CanonicalCodecError> {
if depth > MAX_NESTING_DEPTH {
return Err(CanonicalCodecError::NestingTooDeep {
depth,
maximum: MAX_NESTING_DEPTH,
});
}
Ok(())
}
fn encode_value(
&mut self,
value: &CanonicalValue,
depth: usize,
) -> Result<(), CanonicalCodecError> {
Self::validate_depth(depth)?;
self.write(&[CANONICAL_VALUE_VERSION])?;
match value {
CanonicalValue::Null => self.write(&[TAG_NULL]),
CanonicalValue::Bool(value) => self.write(&[TAG_BOOL, u8::from(*value)]),
CanonicalValue::I64(value) => {
self.write(&[TAG_I64])?;
self.write(&value.to_be_bytes())
}
CanonicalValue::U64(value) => {
self.write(&[TAG_U64])?;
self.write(&value.to_be_bytes())
}
CanonicalValue::Decimal(value) => {
self.write(&[TAG_DECIMAL])?;
self.encode_decimal(value)
}
CanonicalValue::Money(value) => {
self.write(&[TAG_MONEY])?;
self.write(value.currency().as_bytes())?;
self.encode_decimal(&value.amount())
}
CanonicalValue::String(value) => {
self.write(&[TAG_STRING])?;
self.encode_bounded_bytes(value.as_str().as_bytes(), BoundedKind::String)
}
CanonicalValue::Bytes(value) => {
self.write(&[TAG_BYTES])?;
self.encode_bounded_bytes(value.as_bytes(), BoundedKind::Bytes)
}
CanonicalValue::Timestamp(value) => {
self.write(&[TAG_TIMESTAMP])?;
self.write(&value.seconds().to_be_bytes())?;
self.write(&value.nanoseconds().to_be_bytes())
}
CanonicalValue::Date(value) => {
self.write(&[TAG_DATE])?;
self.write(&value.days_since_unix_epoch().to_be_bytes())
}
CanonicalValue::Uuid(value) => {
self.write(&[TAG_UUID])?;
self.write(value)
}
CanonicalValue::Enum {
type_id,
variant_id,
} => {
self.write(&[TAG_ENUM])?;
self.write(&type_id.get().to_be_bytes())?;
self.write(&variant_id.get().to_be_bytes())
}
CanonicalValue::List(values) => {
if values.len() > MAX_LIST_ENTRIES {
return Err(CanonicalCodecError::TooManyEntries {
kind: CollectionKind::List,
actual: values.len(),
maximum: MAX_LIST_ENTRIES,
});
}
self.write(&[TAG_LIST])?;
self.write(&(values.len() as u32).to_be_bytes())?;
for value in values.values() {
self.encode_value(value, depth + 1)?;
}
Ok(())
}
CanonicalValue::Record(record) => self.encode_record_payload(record, depth),
CanonicalValue::Vector(vector) => {
self.write(&[TAG_VECTOR])?;
self.write(&vector.dimension().to_be_bytes())?;
for component in vector.components() {
self.write(&component.to_be_bytes())?;
}
Ok(())
}
}
}
fn encode_record_value(
&mut self,
record: &CanonicalRecord,
depth: usize,
) -> Result<(), CanonicalCodecError> {
Self::validate_depth(depth)?;
self.write(&[CANONICAL_VALUE_VERSION])?;
self.encode_record_payload(record, depth)
}
fn encode_record_payload(
&mut self,
record: &CanonicalRecord,
depth: usize,
) -> Result<(), CanonicalCodecError> {
if record.len() > MAX_RECORD_FIELDS {
return Err(CanonicalCodecError::TooManyEntries {
kind: CollectionKind::Record,
actual: record.len(),
maximum: MAX_RECORD_FIELDS,
});
}
self.write(&[TAG_RECORD])?;
self.write(&(record.len() as u32).to_be_bytes())?;
let mut previous = None;
for (field_id, value) in record.fields() {
if previous.is_some_and(|id| id >= field_id.get()) {
return Err(CanonicalCodecError::NonCanonicalRecordOrder);
}
previous = Some(field_id.get());
self.write(&field_id.get().to_be_bytes())?;
self.encode_value(value, depth + 1)?;
}
Ok(())
}
fn encode_decimal(&mut self, value: &Decimal) -> Result<(), CanonicalCodecError> {
self.write(&[value.spec().precision(), value.spec().scale()])?;
self.write(&value.coefficient().to_be_bytes())
}
fn encode_bounded_bytes(
&mut self,
value: &[u8],
kind: BoundedKind,
) -> Result<(), CanonicalCodecError> {
let maximum = match kind {
BoundedKind::String => MAX_STRING_BYTES,
BoundedKind::Bytes => MAX_BYTES_VALUE_BYTES,
};
if value.len() > maximum {
return Err(match kind {
BoundedKind::String => CanonicalCodecError::StringTooLarge {
actual: value.len(),
maximum,
},
BoundedKind::Bytes => CanonicalCodecError::BytesTooLarge {
actual: value.len(),
maximum,
},
});
}
self.write(&(value.len() as u32).to_be_bytes())?;
self.write(value)
}
fn write(&mut self, bytes: &[u8]) -> Result<(), CanonicalCodecError> {
let document_len = self.output.len().saturating_sub(self.document_start);
let new_length =
document_len
.checked_add(bytes.len())
.ok_or(CanonicalCodecError::DocumentTooLarge {
actual: usize::MAX,
maximum: self.maximum_document_bytes,
})?;
if new_length > self.maximum_document_bytes {
return Err(CanonicalCodecError::DocumentTooLarge {
actual: new_length,
maximum: self.maximum_document_bytes,
});
}
self.output.extend_from_slice(bytes);
Ok(())
}
}
impl LengthCounter {
fn validate_depth(depth: usize) -> Result<(), CanonicalCodecError> {
Encoder::validate_depth(depth)
}
fn encode_record_value(
&mut self,
record: &CanonicalRecord,
depth: usize,
) -> Result<(), CanonicalCodecError> {
Self::validate_depth(depth)?;
self.add(1)?;
self.encode_record_payload(record, depth)
}
fn encode_value(
&mut self,
value: &CanonicalValue,
depth: usize,
) -> Result<(), CanonicalCodecError> {
Self::validate_depth(depth)?;
self.add(1)?; match value {
CanonicalValue::Null => self.add(1),
CanonicalValue::Bool(_) => self.add(2),
CanonicalValue::I64(_) => self.add(1 + 8),
CanonicalValue::U64(_) => self.add(1 + 8),
CanonicalValue::Decimal(value) => {
self.add(1)?;
self.encode_decimal(value)
}
CanonicalValue::Money(value) => {
self.add(1 + 3)?;
self.encode_decimal(&value.amount())
}
CanonicalValue::String(value) => {
self.add(1)?;
self.encode_bounded_bytes(value.as_str().as_bytes(), BoundedKind::String)
}
CanonicalValue::Bytes(value) => {
self.add(1)?;
self.encode_bounded_bytes(value.as_bytes(), BoundedKind::Bytes)
}
CanonicalValue::Timestamp(_) => self.add(1 + 8 + 4),
CanonicalValue::Date(_) => self.add(1 + 4),
CanonicalValue::Uuid(_) => self.add(1 + 16),
CanonicalValue::Enum { .. } => self.add(1 + 4 + 4),
CanonicalValue::List(values) => {
if values.len() > MAX_LIST_ENTRIES {
return Err(CanonicalCodecError::TooManyEntries {
kind: CollectionKind::List,
actual: values.len(),
maximum: MAX_LIST_ENTRIES,
});
}
self.add(1 + 4)?;
for value in values.values() {
self.encode_value(value, depth + 1)?;
}
Ok(())
}
CanonicalValue::Record(record) => self.encode_record_payload(record, depth),
CanonicalValue::Vector(vector) => {
self.add(1 + 4 + (vector.dimension() as usize) * 4)?;
Ok(())
}
}
}
fn encode_record_payload(
&mut self,
record: &CanonicalRecord,
depth: usize,
) -> Result<(), CanonicalCodecError> {
if record.len() > MAX_RECORD_FIELDS {
return Err(CanonicalCodecError::TooManyEntries {
kind: CollectionKind::Record,
actual: record.len(),
maximum: MAX_RECORD_FIELDS,
});
}
self.add(1 + 4)?;
let mut previous = None;
for (field_id, value) in record.fields() {
if previous.is_some_and(|id| id >= field_id.get()) {
return Err(CanonicalCodecError::NonCanonicalRecordOrder);
}
previous = Some(field_id.get());
self.add(4)?;
self.encode_value(value, depth + 1)?;
}
Ok(())
}
fn encode_decimal(&mut self, value: &Decimal) -> Result<(), CanonicalCodecError> {
let _ = value;
self.add(2 + 16)
}
fn encode_bounded_bytes(
&mut self,
value: &[u8],
kind: BoundedKind,
) -> Result<(), CanonicalCodecError> {
let maximum = match kind {
BoundedKind::String => MAX_STRING_BYTES,
BoundedKind::Bytes => MAX_BYTES_VALUE_BYTES,
};
if value.len() > maximum {
return Err(match kind {
BoundedKind::String => CanonicalCodecError::StringTooLarge {
actual: value.len(),
maximum,
},
BoundedKind::Bytes => CanonicalCodecError::BytesTooLarge {
actual: value.len(),
maximum,
},
});
}
self.add(4 + value.len())
}
fn add(&mut self, bytes: usize) -> Result<(), CanonicalCodecError> {
let new_length =
self.len
.checked_add(bytes)
.ok_or(CanonicalCodecError::DocumentTooLarge {
actual: usize::MAX,
maximum: self.maximum_document_bytes,
})?;
if new_length > self.maximum_document_bytes {
return Err(CanonicalCodecError::DocumentTooLarge {
actual: new_length,
maximum: self.maximum_document_bytes,
});
}
self.len = new_length;
Ok(())
}
}
struct Decoder<'a> {
input: &'a [u8],
position: usize,
}
impl Decoder<'_> {
fn decode_value(&mut self, depth: usize) -> Result<CanonicalValue, CanonicalCodecError> {
if depth > MAX_NESTING_DEPTH {
return Err(CanonicalCodecError::NestingTooDeep {
depth,
maximum: MAX_NESTING_DEPTH,
});
}
let version = self.read_u8()?;
if version != CANONICAL_VALUE_VERSION {
return Err(CanonicalCodecError::UnsupportedVersion { version });
}
let tag = self.read_u8()?;
match tag {
TAG_NULL => Ok(CanonicalValue::Null),
TAG_BOOL => match self.read_u8()? {
0 => Ok(CanonicalValue::Bool(false)),
1 => Ok(CanonicalValue::Bool(true)),
value => Err(CanonicalCodecError::InvalidBoolean { value }),
},
TAG_I64 => Ok(CanonicalValue::I64(i64::from_be_bytes(self.read_array()?))),
TAG_U64 => Ok(CanonicalValue::U64(u64::from_be_bytes(self.read_array()?))),
TAG_DECIMAL => Ok(CanonicalValue::Decimal(self.decode_decimal()?)),
TAG_MONEY => {
let currency_bytes: [u8; 3] = self.read_array()?;
let currency_text = std::str::from_utf8(¤cy_bytes)
.map_err(|_| CanonicalCodecError::InvalidCurrency)?;
let currency = CurrencyCode::new(currency_text)
.map_err(|_| CanonicalCodecError::InvalidCurrency)?;
let amount = self.decode_decimal()?;
Ok(CanonicalValue::Money(Money::new(currency, amount)))
}
TAG_STRING => {
let bytes = self.decode_bounded_bytes(BoundedKind::String)?;
let value = std::str::from_utf8(bytes)
.map_err(|_| CanonicalCodecError::InvalidUtf8)?
.to_owned();
Ok(CanonicalValue::String(
CanonicalString::new(value).map_err(|_| {
CanonicalCodecError::StringTooLarge {
actual: bytes.len(),
maximum: MAX_STRING_BYTES,
}
})?,
))
}
TAG_BYTES => {
let bytes = self.decode_bounded_bytes(BoundedKind::Bytes)?;
Ok(CanonicalValue::Bytes(
CanonicalBytes::new(bytes.to_vec()).map_err(|_| {
CanonicalCodecError::BytesTooLarge {
actual: bytes.len(),
maximum: MAX_BYTES_VALUE_BYTES,
}
})?,
))
}
TAG_TIMESTAMP => {
let seconds = i64::from_be_bytes(self.read_array()?);
let nanos = u32::from_be_bytes(self.read_array()?);
Timestamp::new(seconds, nanos)
.map(CanonicalValue::Timestamp)
.map_err(|_| CanonicalCodecError::InvalidTimestamp)
}
TAG_DATE => Ok(CanonicalValue::Date(Date::from_days_since_unix_epoch(
i32::from_be_bytes(self.read_array()?),
))),
TAG_UUID => Ok(CanonicalValue::Uuid(self.read_array()?)),
TAG_ENUM => {
let type_id = EnumTypeId::new(u32::from_be_bytes(self.read_array()?))
.ok_or(CanonicalCodecError::ZeroEnumTypeId)?;
let variant_id = EnumVariantId::new(u32::from_be_bytes(self.read_array()?))
.ok_or(CanonicalCodecError::ZeroEnumVariantId)?;
Ok(CanonicalValue::Enum {
type_id,
variant_id,
})
}
TAG_LIST => {
let count = self.read_collection_count(CollectionKind::List)?;
let mut values = Vec::with_capacity(count);
for _ in 0..count {
values.push(self.decode_value(depth + 1)?);
}
Ok(CanonicalValue::List(CanonicalList::new(values).map_err(
|_| CanonicalCodecError::TooManyEntries {
kind: CollectionKind::List,
actual: count,
maximum: MAX_LIST_ENTRIES,
},
)?))
}
TAG_RECORD => {
let count = self.read_collection_count(CollectionKind::Record)?;
let mut fields = Vec::with_capacity(count);
let mut previous = None;
for _ in 0..count {
let raw_field_id = u32::from_be_bytes(self.read_array()?);
if previous.is_some_and(|id| id >= raw_field_id) {
return Err(CanonicalCodecError::NonCanonicalRecordOrder);
}
previous = Some(raw_field_id);
let field_id =
FieldId::new(raw_field_id).ok_or(CanonicalCodecError::ZeroFieldId)?;
fields.push((field_id, self.decode_value(depth + 1)?));
}
Ok(CanonicalValue::Record(
CanonicalRecord::from_canonical_fields(fields),
))
}
TAG_VECTOR => {
let dimension = u32::from_be_bytes(self.read_array()?);
if dimension == 0 || dimension > crate::MAX_VECTOR_DIMENSION {
return Err(CanonicalCodecError::VectorDimensionOutOfRange {
actual: dimension,
maximum: crate::MAX_VECTOR_DIMENSION,
});
}
let mut components = Vec::with_capacity(dimension as usize);
for index in 0..dimension as usize {
let bits = u32::from_be_bytes(self.read_array()?);
let component = f32::from_bits(bits);
if !component.is_finite() || bits == (-0.0_f32).to_bits() {
return Err(CanonicalCodecError::NonCanonicalVectorComponent { index });
}
components.push(component);
}
CanonicalVector::new(components)
.map(CanonicalValue::Vector)
.map_err(|error| match error {
crate::CanonicalVectorError::DimensionOutOfRange { .. } => {
CanonicalCodecError::VectorDimensionOutOfRange {
actual: dimension,
maximum: crate::MAX_VECTOR_DIMENSION,
}
}
crate::CanonicalVectorError::NonFiniteComponent { index } => {
CanonicalCodecError::NonCanonicalVectorComponent { index }
}
})
}
tag => Err(CanonicalCodecError::UnknownTag { tag }),
}
}
fn decode_decimal(&mut self) -> Result<Decimal, CanonicalCodecError> {
let precision = self.read_u8()?;
let scale = self.read_u8()?;
let coefficient = i128::from_be_bytes(self.read_array()?);
let spec =
DecimalSpec::new(precision, scale).map_err(|_| CanonicalCodecError::InvalidDecimal)?;
Decimal::new(spec, coefficient).map_err(|_| CanonicalCodecError::InvalidDecimal)
}
fn decode_bounded_bytes(&mut self, kind: BoundedKind) -> Result<&[u8], CanonicalCodecError> {
let length = u32::from_be_bytes(self.read_array()?) as usize;
let maximum = match kind {
BoundedKind::String => MAX_STRING_BYTES,
BoundedKind::Bytes => MAX_BYTES_VALUE_BYTES,
};
if length > maximum {
return Err(match kind {
BoundedKind::String => CanonicalCodecError::StringTooLarge {
actual: length,
maximum,
},
BoundedKind::Bytes => CanonicalCodecError::BytesTooLarge {
actual: length,
maximum,
},
});
}
self.read(length)
}
fn read_collection_count(
&mut self,
kind: CollectionKind,
) -> Result<usize, CanonicalCodecError> {
let count = u32::from_be_bytes(self.read_array()?) as usize;
let maximum = match kind {
CollectionKind::List => MAX_LIST_ENTRIES,
CollectionKind::Record => MAX_RECORD_FIELDS,
};
if count > maximum {
return Err(CanonicalCodecError::TooManyEntries {
kind,
actual: count,
maximum,
});
}
Ok(count)
}
fn read_u8(&mut self) -> Result<u8, CanonicalCodecError> {
Ok(self.read(1)?[0])
}
fn read_array<const N: usize>(&mut self) -> Result<[u8; N], CanonicalCodecError> {
self.read(N)?
.try_into()
.map_err(|_| CanonicalCodecError::UnexpectedEnd)
}
fn read(&mut self, length: usize) -> Result<&[u8], CanonicalCodecError> {
let end = self
.position
.checked_add(length)
.ok_or(CanonicalCodecError::UnexpectedEnd)?;
let value = self
.input
.get(self.position..end)
.ok_or(CanonicalCodecError::UnexpectedEnd)?;
self.position = end;
Ok(value)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum BoundedKind {
String,
Bytes,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CollectionKind {
List,
Record,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CanonicalCodecError {
DocumentTooLarge {
actual: usize,
maximum: usize,
},
StringTooLarge {
actual: usize,
maximum: usize,
},
BytesTooLarge {
actual: usize,
maximum: usize,
},
TooManyEntries {
kind: CollectionKind,
actual: usize,
maximum: usize,
},
NestingTooDeep {
depth: usize,
maximum: usize,
},
UnsupportedVersion {
version: u8,
},
UnknownTag {
tag: u8,
},
VectorDimensionOutOfRange {
actual: u32,
maximum: u32,
},
NonCanonicalVectorComponent {
index: usize,
},
InvalidBoolean {
value: u8,
},
InvalidDecimal,
InvalidCurrency,
InvalidTimestamp,
InvalidUtf8,
NonCanonicalRecordOrder,
ZeroEnumTypeId,
ZeroEnumVariantId,
ZeroFieldId,
UnexpectedEnd,
TrailingBytes {
count: usize,
},
}
impl fmt::Display for CanonicalCodecError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DocumentTooLarge { actual, maximum } => write!(
formatter,
"canonical document has {actual} bytes; maximum is {maximum}"
),
Self::StringTooLarge { actual, maximum } => {
write!(formatter, "string has {actual} bytes; maximum is {maximum}")
}
Self::BytesTooLarge { actual, maximum } => {
write!(
formatter,
"byte value has {actual} bytes; maximum is {maximum}"
)
}
Self::TooManyEntries {
kind,
actual,
maximum,
} => write!(
formatter,
"{kind:?} has {actual} entries; maximum is {maximum}"
),
Self::NestingTooDeep { depth, maximum } => {
write!(formatter, "nesting depth is {depth}; maximum is {maximum}")
}
Self::UnsupportedVersion { version } => {
write!(formatter, "unsupported canonical value version {version}")
}
Self::UnknownTag { tag } => write!(formatter, "unknown canonical value tag {tag}"),
Self::VectorDimensionOutOfRange { actual, maximum } => write!(
formatter,
"vector dimension {actual} is outside 1..={maximum}"
),
Self::NonCanonicalVectorComponent { index } => write!(
formatter,
"vector component {index} is not in canonical form (NaN, infinity, or -0.0)"
),
Self::InvalidBoolean { value } => {
write!(formatter, "invalid Boolean payload {value}")
}
Self::InvalidDecimal => formatter.write_str("invalid canonical decimal"),
Self::InvalidCurrency => formatter.write_str("invalid canonical currency"),
Self::InvalidTimestamp => formatter.write_str("invalid canonical timestamp"),
Self::InvalidUtf8 => formatter.write_str("invalid canonical UTF-8"),
Self::NonCanonicalRecordOrder => {
formatter.write_str("record fields are not in canonical order")
}
Self::ZeroEnumTypeId => formatter.write_str("enum type ID must be nonzero"),
Self::ZeroEnumVariantId => formatter.write_str("enum variant ID must be nonzero"),
Self::ZeroFieldId => formatter.write_str("record field ID must be nonzero"),
Self::UnexpectedEnd => formatter.write_str("canonical document ended unexpectedly"),
Self::TrailingBytes { count } => {
write!(formatter, "canonical document has {count} trailing bytes")
}
}
}
}
impl std::error::Error for CanonicalCodecError {}
#[cfg(test)]
mod tests {
use super::*;
fn one_bytes_field(length: usize) -> CanonicalRecord {
CanonicalRecord::new(vec![(
FieldId::new(1).expect("nonzero field ID"),
CanonicalValue::bytes(vec![0xa5; length]).expect("bounded byte value"),
)])
.expect("bounded canonical record")
}
#[test]
fn borrowed_record_encoder_preserves_complete_value_record_bytes() {
let record = CanonicalRecord::new(vec![
(
FieldId::new(2).expect("field ID"),
CanonicalValue::Bool(true),
),
(FieldId::new(1).expect("field ID"), CanonicalValue::U64(42)),
])
.expect("canonical record");
assert_eq!(
encode_canonical_record(&record),
encode_canonical_value(&CanonicalValue::Record(record.clone()))
);
assert_eq!(
encode_canonical_record(&record).expect("borrowed encoding")[..2],
[CANONICAL_VALUE_VERSION, TAG_RECORD]
);
}
#[test]
fn atomic_command_input_envelope_is_additive_and_preserves_v1_bytes() {
let legacy = one_bytes_field(128);
assert_eq!(
encode_canonical_command_input(&legacy).expect("command input"),
encode_canonical_record(&legacy).expect("legacy record")
);
let large = CanonicalRecord::new(vec![
(
FieldId::new(1).expect("field ID"),
CanonicalValue::bytes(vec![0xa5; 900_000]).expect("first value"),
),
(
FieldId::new(2).expect("field ID"),
CanonicalValue::bytes(vec![0x5a; 900_000]).expect("second value"),
),
])
.expect("large command input");
assert!(matches!(
encode_canonical_record(&large),
Err(CanonicalCodecError::DocumentTooLarge {
maximum: MAX_CANONICAL_DOCUMENT_BYTES,
..
})
));
let encoded = encode_canonical_command_input(&large).expect("large command input encode");
assert_eq!(
canonical_command_input_encoded_len(&large).expect("large command input length"),
encoded.len()
);
assert!(encoded.len() > MAX_CANONICAL_DOCUMENT_BYTES);
assert!(encoded.len() <= MAX_ATOMIC_COMMAND_REQUEST_BYTES_V2);
}
#[test]
fn borrowed_record_encoder_accepts_exact_document_maximum_and_rejects_one_over() {
const RECORD_OVERHEAD: usize = 16;
let exact = one_bytes_field(MAX_CANONICAL_DOCUMENT_BYTES - RECORD_OVERHEAD);
assert_eq!(
encode_canonical_record(&exact)
.expect("exact maximum canonical record")
.len(),
MAX_CANONICAL_DOCUMENT_BYTES
);
let over = one_bytes_field(MAX_CANONICAL_DOCUMENT_BYTES - RECORD_OVERHEAD + 1);
assert_eq!(
encode_canonical_record(&over),
Err(CanonicalCodecError::DocumentTooLarge {
actual: MAX_CANONICAL_DOCUMENT_BYTES + 1,
maximum: MAX_CANONICAL_DOCUMENT_BYTES,
})
);
}
#[test]
fn vector_decode_rejects_non_canonical_bytes() {
let zero_dimension = [1u8, TAG_VECTOR, 0, 0, 0, 0];
assert_eq!(
decode_canonical_value(&zero_dimension),
Err(CanonicalCodecError::VectorDimensionOutOfRange {
actual: 0,
maximum: crate::MAX_VECTOR_DIMENSION,
})
);
let mut oversized = vec![1u8, TAG_VECTOR];
oversized.extend((crate::MAX_VECTOR_DIMENSION + 1).to_be_bytes());
assert_eq!(
decode_canonical_value(&oversized),
Err(CanonicalCodecError::VectorDimensionOutOfRange {
actual: crate::MAX_VECTOR_DIMENSION + 1,
maximum: crate::MAX_VECTOR_DIMENSION,
})
);
for (bits, index) in [
(f32::NAN.to_bits(), 1u32),
(f32::INFINITY.to_bits(), 1),
(f32::NEG_INFINITY.to_bits(), 1),
((-0.0_f32).to_bits(), 1),
] {
let mut document = vec![1u8, TAG_VECTOR, 0, 0, 0, 2];
document.extend(1.0_f32.to_be_bytes());
document.extend(bits.to_be_bytes());
assert_eq!(
decode_canonical_value(&document),
Err(CanonicalCodecError::NonCanonicalVectorComponent {
index: index as usize
}),
"bit pattern {bits:#010x} must be rejected"
);
}
for nan_bits in [0x7fc0_0000u32, 0x7fc0_0001] {
let mut document = vec![1u8, TAG_VECTOR, 0, 0, 0, 1];
document.extend(nan_bits.to_be_bytes());
assert!(decode_canonical_value(&document).is_err());
}
}
#[test]
fn rejects_malformed_documents_without_panicking() {
let cases: &[&[u8]] = &[
&[],
&[2, TAG_NULL],
&[1, 0xff],
&[1, TAG_BOOL, 2],
&[1, TAG_U64],
&[1, TAG_STRING, 0, 0, 0, 1, 0xff],
&[1, TAG_NULL, 0],
];
for case in cases {
assert!(decode_canonical_value(case).is_err(), "accepted {case:?}");
}
}
#[test]
fn encoded_len_matches_encode_across_variants_and_nesting() {
use crate::{
CurrencyCode, Date, Decimal, DecimalSpec, EnumTypeId, EnumVariantId, Money, Timestamp,
};
let decimal = Decimal::new(DecimalSpec::new(10, 2).expect("spec"), 1_234).expect("decimal");
let money = Money::new(CurrencyCode::new("USD").expect("currency"), decimal);
let nested_record = CanonicalRecord::new(vec![
(FieldId::new(1).expect("id"), CanonicalValue::U64(7)),
(
FieldId::new(3).expect("id"),
CanonicalValue::String(CanonicalString::new("nested").expect("string")),
),
])
.expect("record");
let nested_list = CanonicalList::new(vec![
CanonicalValue::Bool(true),
CanonicalValue::I64(-9),
CanonicalValue::Record(nested_record.clone()),
])
.expect("list");
let samples = [
CanonicalValue::Null,
CanonicalValue::Bool(false),
CanonicalValue::Bool(true),
CanonicalValue::I64(i64::MIN),
CanonicalValue::I64(0),
CanonicalValue::I64(i64::MAX),
CanonicalValue::U64(0),
CanonicalValue::U64(u64::MAX),
CanonicalValue::Decimal(decimal),
CanonicalValue::Money(money),
CanonicalValue::String(CanonicalString::new("").expect("empty")),
CanonicalValue::String(CanonicalString::new("hello ✓").expect("utf8")),
CanonicalValue::Bytes(CanonicalBytes::new(vec![]).expect("empty bytes")),
CanonicalValue::Bytes(CanonicalBytes::new(vec![0, 1, 2, 255]).expect("bytes")),
CanonicalValue::Timestamp(Timestamp::new(1_700_000_000, 123).expect("ts")),
CanonicalValue::Date(Date::from_days_since_unix_epoch(20_000)),
CanonicalValue::Uuid([0x11; 16]),
CanonicalValue::Enum {
type_id: EnumTypeId::new(4).expect("type"),
variant_id: EnumVariantId::new(2).expect("variant"),
},
CanonicalValue::List(CanonicalList::new(vec![]).expect("empty list")),
CanonicalValue::List(nested_list),
CanonicalValue::Record(CanonicalRecord::new(vec![]).expect("empty record")),
CanonicalValue::Record(nested_record),
CanonicalValue::Vector(CanonicalVector::new(vec![1.0]).expect("finite")),
CanonicalValue::Vector(
CanonicalVector::new(vec![0.0, -2.5, 3.25, f32::MIN, f32::MAX]).expect("finite"),
),
];
for value in samples {
let encoded = encode_canonical_value(&value).expect("encode");
let len = canonical_value_encoded_len(&value).expect("length");
assert_eq!(
len,
encoded.len(),
"encoded_len diverged for {value:?}: len={len} encode={}",
encoded.len()
);
let mut into = Vec::new();
encode_canonical_value_into(&mut into, &value).expect("encode_into");
assert_eq!(
into, encoded,
"encode_into diverged from encode_canonical_value for {value:?}"
);
let mut packed = vec![0xAA, 0xBB];
encode_canonical_value_into(&mut packed, &value).expect("append encode_into");
assert_eq!(&packed[2..], encoded.as_slice());
assert_eq!(
encoded.capacity(),
encoded.len(),
"encode_canonical_value over-or-under reserved for {value:?}"
);
}
}
#[test]
fn record_encoded_len_is_exact_and_reserves_once() {
use crate::{
CurrencyCode, Date, Decimal, DecimalSpec, EnumTypeId, EnumVariantId, Money, Timestamp,
};
let decimal = Decimal::new(DecimalSpec::new(10, 2).expect("spec"), 1_234).expect("decimal");
let inner = CanonicalRecord::new(vec![
(FieldId::new(2).expect("id"), CanonicalValue::I64(-1)),
(
FieldId::new(5).expect("id"),
CanonicalValue::Vector(CanonicalVector::new(vec![1.0, -2.0, 3.5]).expect("finite")),
),
])
.expect("inner record");
let records = [
CanonicalRecord::new(vec![]).expect("empty record"),
CanonicalRecord::new(vec![(FieldId::new(1).expect("id"), CanonicalValue::Null)])
.expect("single null"),
CanonicalRecord::new(vec![
(FieldId::new(1).expect("id"), CanonicalValue::Uuid([7; 16])),
(
FieldId::new(2).expect("id"),
CanonicalValue::String(
CanonicalString::new("wide title ✓".repeat(24)).expect("string"),
),
),
(
FieldId::new(3).expect("id"),
CanonicalValue::Timestamp(Timestamp::new(1_700_000_000, 7).expect("ts")),
),
(
FieldId::new(4).expect("id"),
CanonicalValue::Decimal(decimal),
),
(
FieldId::new(5).expect("id"),
CanonicalValue::Money(Money::new(
CurrencyCode::new("EUR").expect("currency"),
decimal,
)),
),
(
FieldId::new(6).expect("id"),
CanonicalValue::Date(Date::from_days_since_unix_epoch(19_000)),
),
(
FieldId::new(7).expect("id"),
CanonicalValue::Enum {
type_id: EnumTypeId::new(3).expect("type"),
variant_id: EnumVariantId::new(1).expect("variant"),
},
),
(
FieldId::new(8).expect("id"),
CanonicalValue::Bytes(CanonicalBytes::new(vec![9; 300]).expect("bytes")),
),
(
FieldId::new(9).expect("id"),
CanonicalValue::List(
CanonicalList::new(vec![
CanonicalValue::Record(inner.clone()),
CanonicalValue::Bool(true),
])
.expect("list"),
),
),
])
.expect("wide record"),
];
for record in records {
let encoded = encode_canonical_record(&record).expect("encode");
let len = canonical_record_encoded_len(&record).expect("length");
assert_eq!(len, encoded.len(), "record length diverged from encode");
assert_eq!(
encoded.capacity(),
encoded.len(),
"encode_canonical_record grew or over-reserved its buffer"
);
let as_value = encode_canonical_value(&CanonicalValue::Record(record.clone()))
.expect("value encode");
assert_eq!(as_value, encoded, "record and value encodings diverged");
assert_eq!(
canonical_value_encoded_len(&CanonicalValue::Record(record)).expect("value length"),
len,
"record and value lengths diverged"
);
}
}
#[test]
fn encode_into_byte_identity_detects_divergence() {
let value = CanonicalValue::I64(42);
let expected = encode_canonical_value(&value).expect("encode");
let mut into = Vec::new();
encode_canonical_value_into(&mut into, &value).expect("encode_into");
assert_eq!(into, expected);
let mut corrupt = into.clone();
corrupt.push(0xFF);
assert_ne!(
corrupt, expected,
"appended garbage must break byte-identity"
);
}
#[test]
fn failed_encode_into_leaves_the_buffer_untouched() {
const RECORD_OVERHEAD: usize = 16;
let over = CanonicalValue::Record(one_bytes_field(
MAX_CANONICAL_DOCUMENT_BYTES - RECORD_OVERHEAD + 1,
));
let prefix = vec![0xAA, 0xBB, 0xCC];
let mut buffer = prefix.clone();
let error = encode_canonical_value_into(&mut buffer, &over)
.expect_err("oversized document must be rejected");
assert_eq!(
error,
CanonicalCodecError::DocumentTooLarge {
actual: MAX_CANONICAL_DOCUMENT_BYTES + 1,
maximum: MAX_CANONICAL_DOCUMENT_BYTES,
}
);
assert_eq!(
buffer, prefix,
"failed encode must restore the caller's buffer exactly"
);
encode_canonical_value_into(&mut buffer, &CanonicalValue::I64(7))
.expect("encode after err");
assert_eq!(
&buffer[..3],
prefix.as_slice(),
"prefix must survive the recovery append"
);
assert_eq!(
&buffer[3..],
encode_canonical_value(&CanonicalValue::I64(7))
.expect("encode")
.as_slice()
);
}
}