Skip to main content

riffdb_types/
key.rs

1//! Canonical purpose-specific durable key builders.
2
3use std::fmt;
4
5use crate::{
6    AggregateTypeId, ConflictKey, Date, EntityKey, EntityTypeId, EnumVariantId, IndexEntryKey,
7    IndexId, PartitionKey, Timestamp,
8    limits::MAX_KEY_BYTES,
9    {
10        CONFLICT_KEY_V1_PREFIX, ENTITY_KEY_V1_PREFIX, INDEX_ENTRY_KEY_V1_PREFIX,
11        PARTITION_KEY_V1_PREFIX,
12    },
13};
14
15macro_rules! key_component_methods {
16    () => {
17        /// Appends a canonical Boolean component.
18        pub fn push_bool(&mut self, value: bool) -> Result<&mut Self, KeyEncodingError> {
19            self.0.push_fixed(&[u8::from(value)])?;
20            Ok(self)
21        }
22
23        /// Appends an unsigned 32-bit ordered component.
24        pub fn push_u32(&mut self, value: u32) -> Result<&mut Self, KeyEncodingError> {
25            self.0.push_fixed(&value.to_be_bytes())?;
26            Ok(self)
27        }
28
29        /// Appends an unsigned 64-bit ordered component.
30        pub fn push_u64(&mut self, value: u64) -> Result<&mut Self, KeyEncodingError> {
31            self.0.push_fixed(&value.to_be_bytes())?;
32            Ok(self)
33        }
34
35        /// Appends a signed 32-bit component whose bytes preserve numeric order.
36        pub fn push_i32(&mut self, value: i32) -> Result<&mut Self, KeyEncodingError> {
37            let mut bytes = value.to_be_bytes();
38            bytes[0] ^= 0x80;
39            self.0.push_fixed(&bytes)?;
40            Ok(self)
41        }
42
43        /// Appends a signed 64-bit component whose bytes preserve numeric order.
44        pub fn push_i64(&mut self, value: i64) -> Result<&mut Self, KeyEncodingError> {
45            let mut bytes = value.to_be_bytes();
46            bytes[0] ^= 0x80;
47            self.0.push_fixed(&bytes)?;
48            Ok(self)
49        }
50
51        /// Appends a canonical timestamp component.
52        pub fn push_timestamp(&mut self, value: Timestamp) -> Result<&mut Self, KeyEncodingError> {
53            let mut seconds = value.seconds().to_be_bytes();
54            seconds[0] ^= 0x80;
55            let nanoseconds = value.nanoseconds().to_be_bytes();
56            let mut bytes = [0; 12];
57            bytes[..8].copy_from_slice(&seconds);
58            bytes[8..].copy_from_slice(&nanoseconds);
59            self.0.push_fixed(&bytes)?;
60            Ok(self)
61        }
62
63        /// Appends a canonical date component.
64        pub fn push_date(&mut self, value: Date) -> Result<&mut Self, KeyEncodingError> {
65            let mut bytes = value.days_since_unix_epoch().to_be_bytes();
66            bytes[0] ^= 0x80;
67            self.0.push_fixed(&bytes)?;
68            Ok(self)
69        }
70
71        /// Appends a canonical declared-enum variant component.
72        pub fn push_enum_variant(
73            &mut self,
74            value: EnumVariantId,
75        ) -> Result<&mut Self, KeyEncodingError> {
76            self.0.push_fixed(&value.to_be_bytes())?;
77            Ok(self)
78        }
79
80        /// Appends fixed-width UUID network-order bytes.
81        pub fn push_uuid(&mut self, value: &[u8; 16]) -> Result<&mut Self, KeyEncodingError> {
82            self.0.push_fixed(value)?;
83            Ok(self)
84        }
85
86        /// Appends a length-prefixed exact byte component.
87        pub fn push_bytes(&mut self, value: &[u8]) -> Result<&mut Self, KeyEncodingError> {
88            self.0.push_bytes(value)?;
89            Ok(self)
90        }
91
92        /// Appends a length-prefixed exact UTF-8 component without normalization.
93        pub fn push_str(&mut self, value: &str) -> Result<&mut Self, KeyEncodingError> {
94            self.0.push_bytes(value.as_bytes())?;
95            Ok(self)
96        }
97
98        /// Appends zero-escaped bytes whose encoded order preserves byte order.
99        pub fn push_ordered_bytes(&mut self, value: &[u8]) -> Result<&mut Self, KeyEncodingError> {
100            self.0.push_ordered_bytes(value, true)?;
101            Ok(self)
102        }
103
104        /// Appends the non-terminated prefix of one ordered-byte component.
105        pub fn push_ordered_bytes_prefix(
106            &mut self,
107            value: &[u8],
108        ) -> Result<&mut Self, KeyEncodingError> {
109            self.0.push_ordered_bytes(value, false)?;
110            Ok(self)
111        }
112
113        /// Returns the currently encoded prefix and components.
114        pub fn as_bytes(&self) -> &[u8] {
115            &self.0.bytes
116        }
117    };
118}
119
120/// Builder for a v1 canonical entity key.
121#[derive(Clone)]
122pub struct EntityKeyBuilder(KeyBuilder);
123
124impl EntityKeyBuilder {
125    /// Starts an entity key with its purpose namespace, version, and entity type.
126    pub fn new(entity_type: EntityTypeId) -> Self {
127        Self(KeyBuilder::new(
128            &ENTITY_KEY_V1_PREFIX,
129            entity_type.get().to_be_bytes(),
130        ))
131    }
132
133    key_component_methods!();
134
135    /// Finishes the bounded canonical entity key.
136    pub fn finish(self) -> Result<EntityKey, KeyEncodingError> {
137        Ok(EntityKey::from_validated_bytes(self.0.bytes))
138    }
139}
140
141impl fmt::Debug for EntityKeyBuilder {
142    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
143        formatter
144            .debug_struct("EntityKeyBuilder")
145            .field("bytes", &"[REDACTED]")
146            .field("length", &self.0.bytes.len())
147            .finish()
148    }
149}
150
151/// Builder for a v1 canonical conflict key.
152#[derive(Clone)]
153pub struct ConflictKeyBuilder(KeyBuilder);
154
155impl ConflictKeyBuilder {
156    /// Starts a conflict key with its purpose namespace, version, and aggregate type.
157    pub fn new(aggregate_type: AggregateTypeId) -> Self {
158        Self(KeyBuilder::new(
159            &CONFLICT_KEY_V1_PREFIX,
160            aggregate_type.get().to_be_bytes(),
161        ))
162    }
163
164    key_component_methods!();
165
166    /// Finishes the bounded canonical conflict key.
167    pub fn finish(self) -> Result<ConflictKey, KeyEncodingError> {
168        Ok(ConflictKey::from_validated_bytes(self.0.bytes))
169    }
170}
171
172impl fmt::Debug for ConflictKeyBuilder {
173    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
174        formatter
175            .debug_struct("ConflictKeyBuilder")
176            .field("bytes", &"[REDACTED]")
177            .field("length", &self.0.bytes.len())
178            .finish()
179    }
180}
181
182/// Builder for a v1 canonical logical partition key.
183#[derive(Clone)]
184pub struct PartitionKeyBuilder(KeyBuilder);
185
186impl PartitionKeyBuilder {
187    /// Starts a partition key with its purpose namespace, version, and aggregate type.
188    pub fn new(aggregate_type: AggregateTypeId) -> Self {
189        Self(KeyBuilder::new(
190            &PARTITION_KEY_V1_PREFIX,
191            aggregate_type.get().to_be_bytes(),
192        ))
193    }
194
195    key_component_methods!();
196
197    /// Finishes the bounded canonical logical partition key.
198    pub fn finish(self) -> Result<PartitionKey, KeyEncodingError> {
199        Ok(PartitionKey::from_validated_bytes(self.0.bytes))
200    }
201}
202
203impl fmt::Debug for PartitionKeyBuilder {
204    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
205        formatter
206            .debug_struct("PartitionKeyBuilder")
207            .field("bytes", &"[REDACTED]")
208            .field("length", &self.0.bytes.len())
209            .finish()
210    }
211}
212
213/// Builder for a complete v1 canonical local-index entry key.
214#[derive(Clone)]
215pub struct IndexEntryKeyBuilder(KeyBuilder);
216
217impl IndexEntryKeyBuilder {
218    /// Starts an index entry key with its purpose namespace, version, and index ID.
219    pub fn new(index: IndexId) -> Self {
220        Self(KeyBuilder::new(
221            &INDEX_ENTRY_KEY_V1_PREFIX,
222            index.get().to_be_bytes(),
223        ))
224    }
225
226    key_component_methods!();
227
228    /// Appends the length-delimited complete entity key and finishes the index key.
229    pub fn finish(mut self, entity_key: EntityKey) -> Result<IndexEntryKey, KeyEncodingError> {
230        self.0.push_bytes(entity_key.as_bytes())?;
231        Ok(IndexEntryKey::from_validated_bytes(self.0.bytes))
232    }
233}
234
235impl fmt::Debug for IndexEntryKeyBuilder {
236    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
237        formatter
238            .debug_struct("IndexEntryKeyBuilder")
239            .field("bytes", &"[REDACTED]")
240            .field("length", &self.0.bytes.len())
241            .finish()
242    }
243}
244
245#[derive(Clone)]
246struct KeyBuilder {
247    bytes: Vec<u8>,
248}
249
250impl KeyBuilder {
251    /// Bytes reserved when a key builder starts.
252    ///
253    /// Reserving only the six-byte prefix made every subsequent component push
254    /// grow the vector, so one key encode cost four or five allocations
255    /// (6 -> 8 -> 16 -> 32 -> 64) on the single writer thread. Reserving a
256    /// whole typical key up front makes it one. This is a capacity hint only:
257    /// the encoded bytes, the component bounds and `MAX_KEY_BYTES` are
258    /// unchanged, and a longer key still grows exactly as it did before.
259    const INITIAL_CAPACITY: usize = 64;
260
261    fn new(prefix: &[u8], type_id: [u8; 4]) -> Self {
262        let mut bytes = Vec::with_capacity(Self::INITIAL_CAPACITY);
263        bytes.extend_from_slice(prefix);
264        bytes.extend_from_slice(&type_id);
265        Self { bytes }
266    }
267
268    fn push_fixed(&mut self, bytes: &[u8]) -> Result<(), KeyEncodingError> {
269        self.ensure_additional(bytes.len())?;
270        self.bytes.extend_from_slice(bytes);
271        Ok(())
272    }
273
274    fn push_bytes(&mut self, bytes: &[u8]) -> Result<(), KeyEncodingError> {
275        let length =
276            u32::try_from(bytes.len()).map_err(|_| KeyEncodingError::ComponentTooLong {
277                actual: bytes.len(),
278                maximum: u32::MAX as usize,
279            })?;
280        let additional = 4usize
281            .checked_add(bytes.len())
282            .ok_or(KeyEncodingError::TooLong {
283                actual: usize::MAX,
284                maximum: MAX_KEY_BYTES,
285            })?;
286        self.ensure_additional(additional)?;
287        self.bytes.extend_from_slice(&length.to_be_bytes());
288        self.bytes.extend_from_slice(bytes);
289        Ok(())
290    }
291
292    fn push_ordered_bytes(
293        &mut self,
294        bytes: &[u8],
295        terminated: bool,
296    ) -> Result<(), KeyEncodingError> {
297        let zeros = bytes.iter().filter(|byte| **byte == 0).count();
298        let additional = bytes
299            .len()
300            .checked_add(zeros)
301            .and_then(|value| value.checked_add(if terminated { 2 } else { 0 }))
302            .ok_or(KeyEncodingError::TooLong {
303                actual: usize::MAX,
304                maximum: MAX_KEY_BYTES,
305            })?;
306        self.ensure_additional(additional)?;
307        for byte in bytes {
308            if *byte == 0 {
309                self.bytes.extend_from_slice(&[0, 0xff]);
310            } else {
311                self.bytes.push(*byte);
312            }
313        }
314        if terminated {
315            self.bytes.extend_from_slice(&[0, 0]);
316        }
317        Ok(())
318    }
319
320    fn ensure_additional(&self, additional: usize) -> Result<(), KeyEncodingError> {
321        let actual = self
322            .bytes
323            .len()
324            .checked_add(additional)
325            .ok_or(KeyEncodingError::TooLong {
326                actual: usize::MAX,
327                maximum: MAX_KEY_BYTES,
328            })?;
329        if actual > MAX_KEY_BYTES {
330            return Err(KeyEncodingError::TooLong {
331                actual,
332                maximum: MAX_KEY_BYTES,
333            });
334        }
335        Ok(())
336    }
337}
338
339/// A bounded key-encoding failure.
340#[derive(Clone, Debug, Eq, PartialEq)]
341pub enum KeyEncodingError {
342    /// The complete key would exceed the 4 KiB hard limit.
343    TooLong {
344        /// Attempted encoded key size.
345        actual: usize,
346        /// Maximum encoded key size.
347        maximum: usize,
348    },
349    /// A variable component cannot be represented by its u32 length prefix.
350    ComponentTooLong {
351        /// Component byte length.
352        actual: usize,
353        /// Maximum representable component byte length.
354        maximum: usize,
355    },
356}
357
358impl fmt::Display for KeyEncodingError {
359    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
360        match self {
361            Self::TooLong { actual, maximum } => {
362                write!(
363                    formatter,
364                    "key would have {actual} bytes; maximum is {maximum}"
365                )
366            }
367            Self::ComponentTooLong { actual, maximum } => write!(
368                formatter,
369                "key component has {actual} bytes; maximum is {maximum}"
370            ),
371        }
372    }
373}
374
375impl std::error::Error for KeyEncodingError {}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380
381    #[test]
382    fn key_identity_is_part_of_the_immutable_prefix() {
383        assert_eq!(
384            EntityKeyBuilder::new(EntityTypeId::new(0x0102_0304).expect("nonzero")).as_bytes(),
385            &[0x45, 0x01, 0x01, 0x02, 0x03, 0x04]
386        );
387        assert_eq!(
388            ConflictKeyBuilder::new(AggregateTypeId::new(0x0506_0708).expect("nonzero")).as_bytes(),
389            &[0x43, 0x01, 0x05, 0x06, 0x07, 0x08]
390        );
391        assert_eq!(
392            PartitionKeyBuilder::new(AggregateTypeId::new(0x090a_0b0c).expect("nonzero"))
393                .as_bytes(),
394            &[0x50, 0x01, 0x09, 0x0a, 0x0b, 0x0c]
395        );
396        assert_eq!(
397            IndexEntryKeyBuilder::new(IndexId::new(0x0d0e_0f10).expect("nonzero")).as_bytes(),
398            &[0x49, 0x01, 0x0d, 0x0e, 0x0f, 0x10]
399        );
400    }
401
402    #[test]
403    fn failed_append_does_not_mutate_builder() {
404        let mut builder = EntityKeyBuilder::new(EntityTypeId::first());
405        let before = builder.as_bytes().to_vec();
406        assert!(builder.push_bytes(&vec![0; MAX_KEY_BYTES]).is_err());
407        assert_eq!(builder.as_bytes(), before);
408    }
409}