Skip to main content

reifydb_codec/row/envelope/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_value::{
5	encoding::LeBytes,
6	error::{Error as ValueError, TypeError},
7	util::cowvec::CowVec,
8	value::datetime::DateTime,
9};
10use thiserror::Error;
11
12use crate::row::{bytes::EncodedBytes, pod::EncodedPodRow, shape::fingerprint::RowShapeFingerprint};
13
14pub const HAS_CREATED_AT: u8 = 1 << 0;
15
16pub const HAS_UPDATED_AT: u8 = 1 << 1;
17
18pub const HAS_TIME: u8 = 1 << 2;
19
20pub const HAS_FINGERPRINT: u8 = 1 << 3;
21
22pub const ENVELOPE_FLAGS_SIZE: usize = 1;
23
24pub const ENVELOPE_FIELD_SIZE: usize = DateTime::ENCODED_SIZE;
25
26#[inline]
27pub const fn header_size(flags: u8) -> usize {
28	ENVELOPE_FLAGS_SIZE + ENVELOPE_FIELD_SIZE * flags.count_ones() as usize
29}
30
31#[inline]
32const fn field_offset(flags: u8, bit: u8) -> usize {
33	ENVELOPE_FLAGS_SIZE + ENVELOPE_FIELD_SIZE * (flags & (bit - 1)).count_ones() as usize
34}
35
36#[derive(Debug, Error, PartialEq)]
37pub enum EnvelopeError {
38	#[error("envelope row is {len} bytes, too short for the {required} byte header its flags declare")]
39	Truncated {
40		len: usize,
41		required: usize,
42	},
43}
44
45impl From<EnvelopeError> for ValueError {
46	fn from(err: EnvelopeError) -> Self {
47		TypeError::SerdeDeserialize {
48			message: err.to_string(),
49		}
50		.into()
51	}
52}
53
54#[repr(transparent)]
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct Envelope(EncodedBytes);
57
58impl Envelope {
59	pub fn try_view(row: &EncodedPodRow) -> Result<&Self, EnvelopeError> {
60		let bytes = row.bytes();
61		let len = bytes.len();
62		if len < ENVELOPE_FLAGS_SIZE {
63			return Err(EnvelopeError::Truncated {
64				len,
65				required: ENVELOPE_FLAGS_SIZE,
66			});
67		}
68		let required = header_size(bytes[0]);
69		if len < required {
70			return Err(EnvelopeError::Truncated {
71				len,
72				required,
73			});
74		}
75		// SAFETY: Envelope is repr(transparent) over EncodedBytes, so the pointer cast preserves layout,
76		// and the returned reference borrows the same allocation for the same lifetime.
77		Ok(unsafe { &*(bytes as *const EncodedBytes as *const Self) })
78	}
79
80	#[inline]
81	pub fn flags(&self) -> u8 {
82		self.0[0]
83	}
84
85	#[inline]
86	pub fn header_size(&self) -> usize {
87		header_size(self.flags())
88	}
89
90	#[inline]
91	pub fn created_at(&self) -> Option<DateTime> {
92		self.field(HAS_CREATED_AT).map(DateTime::from_le_bytes)
93	}
94
95	#[inline]
96	pub fn updated_at(&self) -> Option<DateTime> {
97		self.field(HAS_UPDATED_AT).map(DateTime::from_le_bytes)
98	}
99
100	#[inline]
101	pub fn time(&self) -> Option<DateTime> {
102		self.field(HAS_TIME).map(DateTime::from_le_bytes)
103	}
104
105	#[inline]
106	pub fn fingerprint(&self) -> Option<RowShapeFingerprint> {
107		self.field(HAS_FINGERPRINT).map(RowShapeFingerprint::from_le_bytes)
108	}
109
110	#[inline]
111	pub fn body(&self) -> &[u8] {
112		&self.0[self.header_size()..]
113	}
114
115	#[inline]
116	fn field(&self, bit: u8) -> Option<[u8; ENVELOPE_FIELD_SIZE]> {
117		let flags = self.flags();
118		if flags & bit == 0 {
119			return None;
120		}
121		let offset = field_offset(flags, bit);
122		Some(self.0[offset..offset + ENVELOPE_FIELD_SIZE]
123			.try_into()
124			.expect("the envelope header is length-checked"))
125	}
126}
127
128#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
129pub struct EnvelopeBuilder {
130	created_at: Option<DateTime>,
131	updated_at: Option<DateTime>,
132	time: Option<DateTime>,
133	fingerprint: Option<RowShapeFingerprint>,
134}
135
136impl EnvelopeBuilder {
137	pub fn new() -> Self {
138		Self::default()
139	}
140
141	pub fn created_at(mut self, created_at: DateTime) -> Self {
142		self.created_at = Some(created_at);
143		self
144	}
145
146	pub fn updated_at(mut self, updated_at: DateTime) -> Self {
147		self.updated_at = Some(updated_at);
148		self
149	}
150
151	pub fn time(mut self, time: DateTime) -> Self {
152		self.time = Some(time);
153		self
154	}
155
156	pub fn fingerprint(mut self, fingerprint: RowShapeFingerprint) -> Self {
157		self.fingerprint = Some(fingerprint);
158		self
159	}
160
161	pub fn flags(&self) -> u8 {
162		let mut flags = 0u8;
163		if self.created_at.is_some() {
164			flags |= HAS_CREATED_AT;
165		}
166		if self.updated_at.is_some() {
167			flags |= HAS_UPDATED_AT;
168		}
169		if self.time.is_some() {
170			flags |= HAS_TIME;
171		}
172		if self.fingerprint.is_some() {
173			flags |= HAS_FINGERPRINT;
174		}
175		flags
176	}
177
178	pub fn build(self, body: &[u8]) -> EncodedPodRow {
179		let flags = self.flags();
180		let mut buffer = Vec::with_capacity(header_size(flags) + body.len());
181		buffer.push(flags);
182		for stamp in [self.created_at, self.updated_at, self.time].into_iter().flatten() {
183			buffer.extend_from_slice(&stamp.to_le_bytes());
184		}
185		if let Some(fingerprint) = self.fingerprint {
186			buffer.extend_from_slice(&fingerprint.to_le_bytes());
187		}
188		buffer.extend_from_slice(body);
189		EncodedPodRow::from(EncodedBytes(CowVec::new(buffer)))
190	}
191}