1use std::fmt;
4
5use crate::{ContractLineage, EntityTypeId, FieldId};
6
7pub const MAX_COLUMNAR_PROJECTION_SOURCE_V1_BYTES: usize = 291;
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub struct ColumnarProjectionReplayLimitsV1 {
13 age_seconds: u64,
14 bytes: u64,
15 backlog: u64,
16}
17
18impl ColumnarProjectionReplayLimitsV1 {
19 #[must_use]
21 pub const fn new(age_seconds: u64, bytes: u64, backlog: u64) -> Option<Self> {
22 if age_seconds == 0 || bytes == 0 || backlog == 0 {
23 return None;
24 }
25 Some(Self {
26 age_seconds,
27 bytes,
28 backlog,
29 })
30 }
31
32 #[must_use]
34 pub const fn age_seconds(self) -> u64 {
35 self.age_seconds
36 }
37
38 #[must_use]
40 pub const fn bytes(self) -> u64 {
41 self.bytes
42 }
43
44 #[must_use]
46 pub const fn backlog(self) -> u64 {
47 self.backlog
48 }
49}
50
51#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
53pub struct DefinitionFingerprint([u8; 32]);
54
55impl DefinitionFingerprint {
56 #[must_use]
58 pub const fn as_bytes(&self) -> &[u8; 32] {
59 &self.0
60 }
61
62 #[must_use]
64 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
65 Self(bytes)
66 }
67}
68
69impl fmt::Display for DefinitionFingerprint {
70 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71 for byte in &self.0 {
72 write!(formatter, "{byte:02x}")?;
73 }
74 Ok(())
75 }
76}
77
78#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
80pub struct VectorProjectionSourceV1 {
81 lineage: ContractLineage,
82 entity_type: EntityTypeId,
83 vector_field: FieldId,
84}
85
86impl VectorProjectionSourceV1 {
87 #[must_use]
89 pub const fn new(
90 lineage: ContractLineage,
91 entity_type: EntityTypeId,
92 vector_field: FieldId,
93 ) -> Self {
94 Self {
95 lineage,
96 entity_type,
97 vector_field,
98 }
99 }
100
101 #[must_use]
103 pub const fn lineage(&self) -> &ContractLineage {
104 &self.lineage
105 }
106
107 #[must_use]
109 pub const fn entity_type(&self) -> EntityTypeId {
110 self.entity_type
111 }
112
113 #[must_use]
115 pub const fn vector_field(&self) -> FieldId {
116 self.vector_field
117 }
118}
119
120#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
122pub enum ColumnarProjectionSourceV1 {
123 Scalar {
125 lineage: ContractLineage,
127 definition_fingerprint: DefinitionFingerprint,
129 },
130 Vector(VectorProjectionSourceV1),
132}
133
134impl ColumnarProjectionSourceV1 {
135 #[must_use]
137 pub const fn scalar(
138 lineage: ContractLineage,
139 definition_fingerprint: DefinitionFingerprint,
140 ) -> Self {
141 Self::Scalar {
142 lineage,
143 definition_fingerprint,
144 }
145 }
146
147 #[must_use]
149 pub const fn vector(source: VectorProjectionSourceV1) -> Self {
150 Self::Vector(source)
151 }
152
153 #[must_use]
155 pub const fn lineage(&self) -> &ContractLineage {
156 match self {
157 Self::Scalar { lineage, .. } => lineage,
158 Self::Vector(source) => source.lineage(),
159 }
160 }
161
162 #[must_use]
164 pub fn to_canonical_bytes(&self) -> Vec<u8> {
165 let lineage = self.lineage().as_str().as_bytes();
166 let mut bytes = Vec::with_capacity(1 + 2 + lineage.len() + 32);
167 match self {
168 Self::Scalar {
169 definition_fingerprint,
170 ..
171 } => {
172 bytes.push(0x01);
173 bytes.extend_from_slice(&(lineage.len() as u16).to_be_bytes());
174 bytes.extend_from_slice(lineage);
175 bytes.extend_from_slice(definition_fingerprint.as_bytes());
176 }
177 Self::Vector(source) => {
178 bytes.push(0x02);
179 bytes.extend_from_slice(&(lineage.len() as u16).to_be_bytes());
180 bytes.extend_from_slice(lineage);
181 bytes.extend_from_slice(&source.entity_type().to_be_bytes());
182 bytes.extend_from_slice(&source.vector_field().to_be_bytes());
183 }
184 }
185 bytes
186 }
187
188 pub fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ColumnarProjectionSourceError> {
190 if bytes.len() < 4 || bytes.len() > MAX_COLUMNAR_PROJECTION_SOURCE_V1_BYTES {
191 return Err(ColumnarProjectionSourceError);
192 }
193 let tag = bytes[0];
194 let lineage_len = usize::from(u16::from_be_bytes([bytes[1], bytes[2]]));
195 let suffix = match tag {
196 0x01 => 32,
197 0x02 => 8,
198 _ => return Err(ColumnarProjectionSourceError),
199 };
200 let expected = 3_usize
201 .checked_add(lineage_len)
202 .and_then(|value| value.checked_add(suffix))
203 .ok_or(ColumnarProjectionSourceError)?;
204 if expected != bytes.len() {
205 return Err(ColumnarProjectionSourceError);
206 }
207 let lineage_text = std::str::from_utf8(&bytes[3..3 + lineage_len])
208 .map_err(|_| ColumnarProjectionSourceError)?;
209 let lineage =
210 ContractLineage::new(lineage_text).map_err(|_| ColumnarProjectionSourceError)?;
211 let tail = &bytes[3 + lineage_len..];
212 let source = if tag == 0x01 {
213 let fingerprint: [u8; 32] =
214 tail.try_into().map_err(|_| ColumnarProjectionSourceError)?;
215 Self::scalar(lineage, DefinitionFingerprint::from_bytes(fingerprint))
216 } else {
217 let entity = EntityTypeId::new(u32::from_be_bytes(
218 tail[..4]
219 .try_into()
220 .map_err(|_| ColumnarProjectionSourceError)?,
221 ))
222 .ok_or(ColumnarProjectionSourceError)?;
223 let field = FieldId::new(u32::from_be_bytes(
224 tail[4..]
225 .try_into()
226 .map_err(|_| ColumnarProjectionSourceError)?,
227 ))
228 .ok_or(ColumnarProjectionSourceError)?;
229 Self::vector(VectorProjectionSourceV1::new(lineage, entity, field))
230 };
231 if source.to_canonical_bytes() != bytes {
232 return Err(ColumnarProjectionSourceError);
233 }
234 Ok(source)
235 }
236}
237
238#[derive(Clone, Copy, Debug, Eq, PartialEq)]
240pub struct ColumnarProjectionSourceError;
241
242impl fmt::Display for ColumnarProjectionSourceError {
243 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
244 formatter.write_str("invalid canonical columnar projection source")
245 }
246}
247
248impl std::error::Error for ColumnarProjectionSourceError {}