1#![expect(clippy::cast_possible_truncation)]
2use crate::{
3 db::{
4 identity::{EntityName, IdentityDecodeError},
5 store::storage_key::{StorageKey, StorageKeyEncodeError},
6 },
7 error::{ErrorClass, ErrorOrigin, InternalError},
8 traits::{EntityKind, FieldValue, Storable},
9};
10use canic_cdk::structures::storable::Bound;
11use std::{
12 borrow::Cow,
13 fmt::{self, Display},
14};
15use thiserror::Error as ThisError;
16
17#[derive(Debug, ThisError)]
23pub enum DataKeyEncodeError {
24 #[error("data key encoding failed for {key}: {source}")]
25 KeyEncoding {
26 key: DataKey,
27 source: StorageKeyEncodeError,
28 },
29}
30
31impl From<DataKeyEncodeError> for InternalError {
32 fn from(err: DataKeyEncodeError) -> Self {
33 Self::new(
34 ErrorClass::Unsupported,
35 ErrorOrigin::Serialize,
36 err.to_string(),
37 )
38 }
39}
40
41#[derive(Debug, ThisError)]
47pub enum KeyDecodeError {
48 #[error("invalid primary key encoding")]
49 InvalidEncoding,
50}
51
52impl From<&'static str> for KeyDecodeError {
53 fn from(_: &'static str) -> Self {
54 Self::InvalidEncoding
55 }
56}
57
58#[derive(Debug, ThisError)]
64pub enum DataKeyDecodeError {
65 #[error("invalid entity name")]
66 Entity(#[from] IdentityDecodeError),
67
68 #[error("invalid primary key")]
69 Key(#[from] KeyDecodeError),
70}
71
72#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
77pub struct DataKey {
78 entity: EntityName,
79 key: StorageKey,
80}
81
82impl DataKey {
83 pub const STORED_SIZE_BYTES: u64 =
85 EntityName::STORED_SIZE_BYTES + StorageKey::STORED_SIZE_BYTES;
86
87 pub const STORED_SIZE_USIZE: usize = Self::STORED_SIZE_BYTES as usize;
89
90 pub fn try_new<E>(key: E::Key) -> Result<Self, InternalError>
98 where
99 E: EntityKind,
100 {
101 let value = key.to_value();
102 let key = StorageKey::try_from_value(&value)?;
103
104 Ok(Self {
105 entity: Self::entity_for::<E>(),
106 key,
107 })
108 }
109
110 pub fn try_key<E>(&self) -> Result<E::Key, InternalError>
115 where
116 E: EntityKind,
117 {
118 let expected = Self::entity_for::<E>();
119 if self.entity != expected {
120 return Err(InternalError::new(
121 ErrorClass::Corruption,
122 ErrorOrigin::Store,
123 format!(
124 "data key entity mismatch: expected {}, found {}",
125 expected, self.entity
126 ),
127 ));
128 }
129
130 let value = self.key.as_value();
131 <E::Key as FieldValue>::from_value(&value).ok_or_else(|| {
132 InternalError::new(
133 ErrorClass::Corruption,
134 ErrorOrigin::Store,
135 format!("data key primary key decode failed: {value:?}"),
136 )
137 })
138 }
139
140 #[must_use]
142 pub fn from_key<E: EntityKind>(key: StorageKey) -> Self {
143 Self {
144 entity: Self::entity_for::<E>(),
145 key,
146 }
147 }
148
149 #[must_use]
150 pub fn lower_bound<E>() -> Self
151 where
152 E: EntityKind,
153 {
154 Self {
155 entity: Self::entity_for::<E>(),
156 key: StorageKey::MIN,
157 }
158 }
159
160 #[must_use]
161 pub fn upper_bound<E>() -> Self
162 where
163 E: EntityKind,
164 {
165 Self {
166 entity: Self::entity_for::<E>(),
167 key: StorageKey::upper_bound(),
168 }
169 }
170
171 #[inline]
172 fn entity_for<E: EntityKind>() -> EntityName {
173 EntityName::try_from_str(E::ENTITY_NAME)
178 .expect("invariant violation: invalid E::ENTITY_NAME (schema/codegen contract broken)")
179 }
180
181 #[must_use]
186 pub const fn entity_name(&self) -> &EntityName {
187 &self.entity
188 }
189
190 #[must_use]
191 pub(crate) const fn storage_key(&self) -> StorageKey {
192 self.key
193 }
194
195 #[must_use]
197 pub const fn entry_size_bytes(value_len: u64) -> u64 {
198 Self::STORED_SIZE_BYTES + value_len
199 }
200
201 #[must_use]
202 pub fn max_storable() -> Self {
203 Self {
204 entity: EntityName::max_storable(),
205 key: StorageKey::max_storable(),
206 }
207 }
208
209 pub fn to_raw(&self) -> Result<RawDataKey, InternalError> {
215 self.to_raw_storage_key_error().map_err(|err| {
216 DataKeyEncodeError::KeyEncoding {
217 key: self.clone(),
218 source: err,
219 }
220 .into()
221 })
222 }
223
224 pub(crate) fn to_raw_storage_key_error(&self) -> Result<RawDataKey, StorageKeyEncodeError> {
226 let mut buf = [0u8; Self::STORED_SIZE_USIZE];
227
228 let entity_bytes = self.entity.to_bytes();
229 buf[..EntityName::STORED_SIZE_USIZE].copy_from_slice(&entity_bytes);
230
231 let key_bytes = self.key.to_bytes()?;
232
233 let key_offset = EntityName::STORED_SIZE_USIZE;
234 buf[key_offset..key_offset + StorageKey::STORED_SIZE_USIZE].copy_from_slice(&key_bytes);
235
236 Ok(RawDataKey(buf))
237 }
238
239 pub(crate) fn raw_from_parts(
241 entity: EntityName,
242 key: StorageKey,
243 ) -> Result<RawDataKey, StorageKeyEncodeError> {
244 Self { entity, key }.to_raw_storage_key_error()
245 }
246
247 pub fn try_from_raw(raw: &RawDataKey) -> Result<Self, DataKeyDecodeError> {
248 let bytes = &raw.0;
249
250 let entity = EntityName::from_bytes(&bytes[..EntityName::STORED_SIZE_USIZE])?;
251
252 let key = StorageKey::try_from_bytes(&bytes[EntityName::STORED_SIZE_USIZE..])
253 .map_err(KeyDecodeError::from)?;
254
255 Ok(Self { entity, key })
256 }
257}
258
259impl Display for DataKey {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 write!(f, "#{} ({})", self.entity, self.key)
262 }
263}
264
265#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
270pub struct RawDataKey([u8; DataKey::STORED_SIZE_USIZE]);
271
272impl RawDataKey {
273 #[must_use]
274 pub const fn as_bytes(&self) -> &[u8; DataKey::STORED_SIZE_USIZE] {
275 &self.0
276 }
277}
278
279impl Storable for RawDataKey {
280 fn to_bytes(&self) -> Cow<'_, [u8]> {
281 Cow::Borrowed(&self.0)
282 }
283
284 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
285 let mut out = [0u8; DataKey::STORED_SIZE_USIZE];
286 if bytes.len() == out.len() {
287 out.copy_from_slice(bytes.as_ref());
288 }
289 Self(out)
290 }
291
292 fn into_bytes(self) -> Vec<u8> {
293 self.0.to_vec()
294 }
295
296 const BOUND: Bound = Bound::Bounded {
297 max_size: DataKey::STORED_SIZE_BYTES as u32,
298 is_fixed_size: true,
299 };
300}
301
302#[cfg(test)]
307mod tests {
308 use super::*;
309 use std::borrow::Cow;
310
311 #[test]
312 fn data_key_is_exactly_fixed_size() {
313 let data_key = DataKey::max_storable();
314 let size = data_key.to_raw().unwrap().as_bytes().len();
315 assert_eq!(size, DataKey::STORED_SIZE_USIZE);
316 }
317
318 #[test]
319 fn data_key_ordering_matches_bytes() {
320 let keys = vec![
321 DataKey {
322 entity: EntityName::try_from_str("a").unwrap(),
323 key: StorageKey::Int(0),
324 },
325 DataKey {
326 entity: EntityName::try_from_str("aa").unwrap(),
327 key: StorageKey::Int(0),
328 },
329 DataKey {
330 entity: EntityName::try_from_str("b").unwrap(),
331 key: StorageKey::Int(0),
332 },
333 DataKey {
334 entity: EntityName::try_from_str("a").unwrap(),
335 key: StorageKey::Uint(1),
336 },
337 ];
338
339 let mut by_ord = keys.clone();
340 by_ord.sort();
341
342 let mut by_bytes = keys;
343 by_bytes.sort_by(|a, b| {
344 a.to_raw()
345 .unwrap()
346 .as_bytes()
347 .cmp(b.to_raw().unwrap().as_bytes())
348 });
349
350 assert_eq!(by_ord, by_bytes);
351 }
352
353 #[test]
354 fn data_key_rejects_corrupt_entity() {
355 let mut raw = DataKey::max_storable().to_raw().unwrap();
356 raw.0[0] = 0;
357 assert!(DataKey::try_from_raw(&raw).is_err());
358 }
359
360 #[test]
361 fn data_key_rejects_corrupt_key() {
362 let mut raw = DataKey::max_storable().to_raw().unwrap();
363 let off = EntityName::STORED_SIZE_USIZE;
364 raw.0[off] = 0xFF;
365 assert!(DataKey::try_from_raw(&raw).is_err());
366 }
367
368 #[test]
369 #[allow(clippy::cast_possible_truncation)]
370 fn data_key_fuzz_roundtrip_is_canonical() {
371 let mut seed = 0xDEAD_BEEF_u64;
372
373 for _ in 0..1_000 {
374 let mut bytes = [0u8; DataKey::STORED_SIZE_USIZE];
375 for b in &mut bytes {
376 seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
377 *b = (seed >> 24) as u8;
378 }
379
380 let raw = RawDataKey(bytes);
381 if let Ok(decoded) = DataKey::try_from_raw(&raw) {
382 let re = decoded.to_raw().unwrap();
383 assert_eq!(raw.as_bytes(), re.as_bytes());
384 }
385 }
386 }
387
388 #[test]
389 fn raw_data_key_storable_roundtrip() {
390 let key = DataKey::max_storable().to_raw().unwrap();
391 let bytes = key.to_bytes();
392 let decoded = RawDataKey::from_bytes(Cow::Borrowed(&bytes));
393 assert_eq!(key, decoded);
394 }
395}