use std::fmt;
use crate::{ContractLineage, EntityTypeId, FieldId};
pub const MAX_COLUMNAR_PROJECTION_SOURCE_V1_BYTES: usize = 291;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ColumnarProjectionReplayLimitsV1 {
age_seconds: u64,
bytes: u64,
backlog: u64,
}
impl ColumnarProjectionReplayLimitsV1 {
#[must_use]
pub const fn new(age_seconds: u64, bytes: u64, backlog: u64) -> Option<Self> {
if age_seconds == 0 || bytes == 0 || backlog == 0 {
return None;
}
Some(Self {
age_seconds,
bytes,
backlog,
})
}
#[must_use]
pub const fn age_seconds(self) -> u64 {
self.age_seconds
}
#[must_use]
pub const fn bytes(self) -> u64 {
self.bytes
}
#[must_use]
pub const fn backlog(self) -> u64 {
self.backlog
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DefinitionFingerprint([u8; 32]);
impl DefinitionFingerprint {
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
#[must_use]
pub const fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
impl fmt::Display for DefinitionFingerprint {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in &self.0 {
write!(formatter, "{byte:02x}")?;
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct VectorProjectionSourceV1 {
lineage: ContractLineage,
entity_type: EntityTypeId,
vector_field: FieldId,
}
impl VectorProjectionSourceV1 {
#[must_use]
pub const fn new(
lineage: ContractLineage,
entity_type: EntityTypeId,
vector_field: FieldId,
) -> Self {
Self {
lineage,
entity_type,
vector_field,
}
}
#[must_use]
pub const fn lineage(&self) -> &ContractLineage {
&self.lineage
}
#[must_use]
pub const fn entity_type(&self) -> EntityTypeId {
self.entity_type
}
#[must_use]
pub const fn vector_field(&self) -> FieldId {
self.vector_field
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum ColumnarProjectionSourceV1 {
Scalar {
lineage: ContractLineage,
definition_fingerprint: DefinitionFingerprint,
},
Vector(VectorProjectionSourceV1),
}
impl ColumnarProjectionSourceV1 {
#[must_use]
pub const fn scalar(
lineage: ContractLineage,
definition_fingerprint: DefinitionFingerprint,
) -> Self {
Self::Scalar {
lineage,
definition_fingerprint,
}
}
#[must_use]
pub const fn vector(source: VectorProjectionSourceV1) -> Self {
Self::Vector(source)
}
#[must_use]
pub const fn lineage(&self) -> &ContractLineage {
match self {
Self::Scalar { lineage, .. } => lineage,
Self::Vector(source) => source.lineage(),
}
}
#[must_use]
pub fn to_canonical_bytes(&self) -> Vec<u8> {
let lineage = self.lineage().as_str().as_bytes();
let mut bytes = Vec::with_capacity(1 + 2 + lineage.len() + 32);
match self {
Self::Scalar {
definition_fingerprint,
..
} => {
bytes.push(0x01);
bytes.extend_from_slice(&(lineage.len() as u16).to_be_bytes());
bytes.extend_from_slice(lineage);
bytes.extend_from_slice(definition_fingerprint.as_bytes());
}
Self::Vector(source) => {
bytes.push(0x02);
bytes.extend_from_slice(&(lineage.len() as u16).to_be_bytes());
bytes.extend_from_slice(lineage);
bytes.extend_from_slice(&source.entity_type().to_be_bytes());
bytes.extend_from_slice(&source.vector_field().to_be_bytes());
}
}
bytes
}
pub fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ColumnarProjectionSourceError> {
if bytes.len() < 4 || bytes.len() > MAX_COLUMNAR_PROJECTION_SOURCE_V1_BYTES {
return Err(ColumnarProjectionSourceError);
}
let tag = bytes[0];
let lineage_len = usize::from(u16::from_be_bytes([bytes[1], bytes[2]]));
let suffix = match tag {
0x01 => 32,
0x02 => 8,
_ => return Err(ColumnarProjectionSourceError),
};
let expected = 3_usize
.checked_add(lineage_len)
.and_then(|value| value.checked_add(suffix))
.ok_or(ColumnarProjectionSourceError)?;
if expected != bytes.len() {
return Err(ColumnarProjectionSourceError);
}
let lineage_text = std::str::from_utf8(&bytes[3..3 + lineage_len])
.map_err(|_| ColumnarProjectionSourceError)?;
let lineage =
ContractLineage::new(lineage_text).map_err(|_| ColumnarProjectionSourceError)?;
let tail = &bytes[3 + lineage_len..];
let source = if tag == 0x01 {
let fingerprint: [u8; 32] =
tail.try_into().map_err(|_| ColumnarProjectionSourceError)?;
Self::scalar(lineage, DefinitionFingerprint::from_bytes(fingerprint))
} else {
let entity = EntityTypeId::new(u32::from_be_bytes(
tail[..4]
.try_into()
.map_err(|_| ColumnarProjectionSourceError)?,
))
.ok_or(ColumnarProjectionSourceError)?;
let field = FieldId::new(u32::from_be_bytes(
tail[4..]
.try_into()
.map_err(|_| ColumnarProjectionSourceError)?,
))
.ok_or(ColumnarProjectionSourceError)?;
Self::vector(VectorProjectionSourceV1::new(lineage, entity, field))
};
if source.to_canonical_bytes() != bytes {
return Err(ColumnarProjectionSourceError);
}
Ok(source)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ColumnarProjectionSourceError;
impl fmt::Display for ColumnarProjectionSourceError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("invalid canonical columnar projection source")
}
}
impl std::error::Error for ColumnarProjectionSourceError {}