1use crate::{
2 key::{StableKey, StableKeyError},
3 schema::{SchemaMetadata, SchemaMetadataError},
4 slot::{AllocationSlot, AllocationSlotDescriptor, MemoryManagerSlotError},
5 text::{DiagnosticTextError, validate_diagnostic_text},
6};
7use serde::{Deserialize, Serialize};
8use std::collections::BTreeSet;
9
10#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
22#[serde(deny_unknown_fields)]
23pub struct AllocationDeclaration {
24 pub(crate) stable_key: StableKey,
26 pub(crate) slot: AllocationSlotDescriptor,
28 #[serde(deserialize_with = "crate::cbor::deserialize_present_option")]
30 pub(crate) label: Option<String>,
31 pub(crate) schema: SchemaMetadata,
33}
34
35impl AllocationDeclaration {
36 pub fn new(
38 stable_key: impl AsRef<str>,
39 slot: AllocationSlotDescriptor,
40 label: Option<String>,
41 schema: SchemaMetadata,
42 ) -> Result<Self, DeclarationSnapshotError> {
43 let stable_key = StableKey::parse(stable_key).map_err(DeclarationSnapshotError::Key)?;
44 slot.validate()
45 .map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
46 validate_label(label.as_deref())?;
47 schema
48 .validate()
49 .map_err(DeclarationSnapshotError::SchemaMetadata)?;
50 Ok(Self {
51 stable_key,
52 slot,
53 label,
54 schema,
55 })
56 }
57
58 pub fn memory_manager(
60 stable_key: impl AsRef<str>,
61 id: u8,
62 label: impl Into<String>,
63 ) -> Result<Self, DeclarationSnapshotError> {
64 Self::memory_manager_with_schema(stable_key, id, label, SchemaMetadata::default())
65 }
66
67 pub fn memory_manager_unlabeled(
69 stable_key: impl AsRef<str>,
70 id: u8,
71 ) -> Result<Self, DeclarationSnapshotError> {
72 Self::memory_manager_unlabeled_with_schema(stable_key, id, SchemaMetadata::default())
73 }
74
75 pub fn memory_manager_with_schema(
77 stable_key: impl AsRef<str>,
78 id: u8,
79 label: impl Into<String>,
80 schema: SchemaMetadata,
81 ) -> Result<Self, DeclarationSnapshotError> {
82 let slot = AllocationSlotDescriptor::memory_manager(id)
83 .map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
84 Self::new(stable_key, slot, Some(label.into()), schema)
85 }
86
87 pub fn memory_manager_unlabeled_with_schema(
89 stable_key: impl AsRef<str>,
90 id: u8,
91 schema: SchemaMetadata,
92 ) -> Result<Self, DeclarationSnapshotError> {
93 let slot = AllocationSlotDescriptor::memory_manager(id)
94 .map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
95 Self::new(stable_key, slot, None, schema)
96 }
97
98 #[must_use]
100 pub const fn stable_key(&self) -> &StableKey {
101 &self.stable_key
102 }
103
104 #[must_use]
106 pub const fn slot(&self) -> &AllocationSlotDescriptor {
107 &self.slot
108 }
109
110 #[must_use]
112 pub fn label(&self) -> Option<&str> {
113 self.label.as_deref()
114 }
115
116 #[must_use]
118 pub const fn schema(&self) -> &SchemaMetadata {
119 &self.schema
120 }
121
122 pub fn validate(&self) -> Result<(), DeclarationSnapshotError> {
124 self.stable_key
125 .validate()
126 .map_err(DeclarationSnapshotError::Key)?;
127 self.slot
128 .validate()
129 .map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
130 validate_label(self.label.as_deref())?;
131 self.schema
132 .validate()
133 .map_err(DeclarationSnapshotError::SchemaMetadata)
134 }
135}
136
137#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
148#[serde(deny_unknown_fields)]
149pub struct DeclarationSnapshot {
150 declarations: Vec<AllocationDeclaration>,
152 #[serde(deserialize_with = "crate::cbor::deserialize_present_option")]
154 runtime_fingerprint: Option<String>,
155}
156
157impl DeclarationSnapshot {
158 pub fn new(declarations: Vec<AllocationDeclaration>) -> Result<Self, DeclarationSnapshotError> {
160 validate_declarations(&declarations)?;
161 reject_duplicates(&declarations)?;
162 Ok(Self {
163 declarations,
164 runtime_fingerprint: None,
165 })
166 }
167
168 pub fn with_runtime_fingerprint(
170 mut self,
171 fingerprint: impl Into<String>,
172 ) -> Result<Self, DeclarationSnapshotError> {
173 let fingerprint = fingerprint.into();
174 validate_runtime_fingerprint(Some(&fingerprint))?;
175 self.runtime_fingerprint = Some(fingerprint);
176 Ok(self)
177 }
178
179 #[must_use]
181 pub const fn is_empty(&self) -> bool {
182 self.declarations.is_empty()
183 }
184
185 #[must_use]
187 pub const fn len(&self) -> usize {
188 self.declarations.len()
189 }
190
191 #[must_use]
193 pub fn declarations(&self) -> &[AllocationDeclaration] {
194 &self.declarations
195 }
196
197 #[must_use]
199 pub fn runtime_fingerprint(&self) -> Option<&str> {
200 self.runtime_fingerprint.as_deref()
201 }
202
203 pub fn validate(&self) -> Result<(), DeclarationSnapshotError> {
205 validate_declarations(&self.declarations)?;
206 reject_duplicates(&self.declarations)?;
207 validate_runtime_fingerprint(self.runtime_fingerprint.as_deref())
208 }
209
210 pub(crate) fn into_parts(self) -> (Vec<AllocationDeclaration>, Option<String>) {
211 (self.declarations, self.runtime_fingerprint)
212 }
213}
214
215#[non_exhaustive]
220#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
221pub enum DeclarationSnapshotError {
222 #[error("at most 255 allocation declarations are supported")]
223 TooManyDeclarations,
224 #[error(transparent)]
226 Key(StableKeyError),
227 #[error(transparent)]
229 MemoryManagerSlot(MemoryManagerSlotError),
230 #[error(transparent)]
232 SchemaMetadata(SchemaMetadataError),
233 #[error("stable key '{0}' is declared more than once")]
235 DuplicateStableKey(StableKey),
236 #[error("allocation slot '{0:?}' is declared more than once")]
238 DuplicateSlot(AllocationSlotDescriptor),
239 #[error("allocation declaration label must not be empty when present")]
241 EmptyLabel,
242 #[error("allocation declaration label must be at most 256 bytes")]
244 LabelTooLong,
245 #[error("allocation declaration label must be ASCII")]
247 NonAsciiLabel,
248 #[error("allocation declaration label must not contain ASCII control characters")]
250 ControlCharacterLabel,
251 #[error("runtime_fingerprint must not be empty when present")]
253 EmptyRuntimeFingerprint,
254 #[error("runtime_fingerprint must be at most 256 bytes")]
256 RuntimeFingerprintTooLong,
257 #[error("runtime_fingerprint must be ASCII")]
259 NonAsciiRuntimeFingerprint,
260 #[error("runtime_fingerprint must not contain ASCII control characters")]
262 ControlCharacterRuntimeFingerprint,
263}
264
265fn validate_label(label: Option<&str>) -> Result<(), DeclarationSnapshotError> {
266 let Some(label) = label else {
267 return Ok(());
268 };
269 validate_diagnostic_text(label).map_err(|error| match error {
270 DiagnosticTextError::Empty => DeclarationSnapshotError::EmptyLabel,
271 DiagnosticTextError::TooLong => DeclarationSnapshotError::LabelTooLong,
272 DiagnosticTextError::NonAscii => DeclarationSnapshotError::NonAsciiLabel,
273 DiagnosticTextError::ControlCharacter => DeclarationSnapshotError::ControlCharacterLabel,
274 })
275}
276
277fn validate_declarations(
278 declarations: &[AllocationDeclaration],
279) -> Result<(), DeclarationSnapshotError> {
280 if declarations.len() > crate::constants::MAX_ALLOCATIONS {
281 return Err(DeclarationSnapshotError::TooManyDeclarations);
282 }
283 for declaration in declarations {
284 declaration.validate()?;
285 }
286 Ok(())
287}
288
289pub fn validate_runtime_fingerprint(
290 fingerprint: Option<&str>,
291) -> Result<(), DeclarationSnapshotError> {
292 let Some(fingerprint) = fingerprint else {
293 return Ok(());
294 };
295 validate_diagnostic_text(fingerprint).map_err(|error| match error {
296 DiagnosticTextError::Empty => DeclarationSnapshotError::EmptyRuntimeFingerprint,
297 DiagnosticTextError::TooLong => DeclarationSnapshotError::RuntimeFingerprintTooLong,
298 DiagnosticTextError::NonAscii => DeclarationSnapshotError::NonAsciiRuntimeFingerprint,
299 DiagnosticTextError::ControlCharacter => {
300 DeclarationSnapshotError::ControlCharacterRuntimeFingerprint
301 }
302 })
303}
304
305fn reject_duplicates(
306 declarations: &[AllocationDeclaration],
307) -> Result<(), DeclarationSnapshotError> {
308 let mut keys = BTreeSet::new();
309 let mut slots = [false; 256];
310
311 for declaration in declarations {
312 let AllocationSlot::MemoryManagerId(id) = declaration.slot.slot();
313 let occupied = &mut slots[usize::from(*id)];
314 if *occupied {
315 return Err(DeclarationSnapshotError::DuplicateSlot(
316 declaration.slot.clone(),
317 ));
318 }
319 *occupied = true;
320 if !keys.insert(&declaration.stable_key) {
321 return Err(DeclarationSnapshotError::DuplicateStableKey(
322 declaration.stable_key.clone(),
323 ));
324 }
325 }
326
327 Ok(())
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333 use crate::slot::AllocationSlotDescriptor;
334
335 fn declaration(key: &str, id: u8) -> AllocationDeclaration {
336 AllocationDeclaration::new(
337 key,
338 AllocationSlotDescriptor::memory_manager(id).expect("usable slot"),
339 None,
340 SchemaMetadata::default(),
341 )
342 .expect("declaration")
343 }
344
345 #[test]
346 fn declaration_rejects_unbounded_label_metadata() {
347 let err = AllocationDeclaration::new(
348 "app.users.v1",
349 AllocationSlotDescriptor::memory_manager(100).expect("usable slot"),
350 Some("x".repeat(257)),
351 SchemaMetadata::default(),
352 )
353 .expect_err("label too long");
354
355 assert_eq!(err, DeclarationSnapshotError::LabelTooLong);
356 }
357
358 #[test]
359 fn memory_manager_declaration_constructor_builds_common_declaration() {
360 let declaration = AllocationDeclaration::memory_manager("app.orders.v1", 100, "orders")
361 .expect("declaration");
362
363 assert_eq!(declaration.stable_key.as_str(), "app.orders.v1");
364 assert_eq!(
365 declaration.slot,
366 AllocationSlotDescriptor::memory_manager(100).expect("usable slot")
367 );
368 assert_eq!(declaration.label.as_deref(), Some("orders"));
369 assert_eq!(declaration.schema, SchemaMetadata::default());
370 }
371
372 #[test]
373 fn memory_manager_declaration_constructor_rejects_invalid_slot() {
374 let err = AllocationDeclaration::memory_manager("app.orders.v1", u8::MAX, "orders")
375 .expect_err("sentinel must fail");
376
377 assert!(matches!(
378 err,
379 DeclarationSnapshotError::MemoryManagerSlot(_)
380 ));
381 }
382
383 #[test]
384 fn snapshot_rejects_decoded_invalid_memory_manager_slot() {
385 let mut declaration = declaration("app.orders.v1", 100);
386 declaration.slot =
387 AllocationSlotDescriptor::memory_manager_unchecked(crate::MEMORY_MANAGER_INVALID_ID);
388
389 let err = DeclarationSnapshot::new(vec![declaration.clone(), declaration])
390 .expect_err("invalid slot must precede duplicate errors");
391
392 assert!(matches!(
393 err,
394 DeclarationSnapshotError::MemoryManagerSlot(
395 MemoryManagerSlotError::InvalidMemoryManagerId { id }
396 ) if id == crate::MEMORY_MANAGER_INVALID_ID
397 ));
398 }
399
400 #[test]
401 fn snapshot_rejects_unbounded_runtime_fingerprint() {
402 let snapshot =
403 DeclarationSnapshot::new(vec![declaration("app.users.v1", 100)]).expect("snapshot");
404
405 let err = snapshot
406 .with_runtime_fingerprint("x".repeat(257))
407 .expect_err("fingerprint too long");
408
409 assert_eq!(err, DeclarationSnapshotError::RuntimeFingerprintTooLong);
410 }
411
412 #[test]
413 fn rejects_duplicate_keys() {
414 let err = DeclarationSnapshot::new(vec![
415 declaration("app.users.v1", 100),
416 declaration("app.users.v1", 101),
417 ])
418 .expect_err("duplicate key");
419
420 assert_eq!(
421 err,
422 DeclarationSnapshotError::DuplicateStableKey(StableKey::parse("app.users.v1").unwrap())
423 );
424 }
425
426 #[test]
427 fn rejects_duplicate_slots() {
428 for second_key in ["app.orders.v1", "app.users.v1"] {
429 let err = DeclarationSnapshot::new(vec![
430 declaration("app.users.v1", 100),
431 declaration(second_key, 100),
432 ])
433 .expect_err("duplicate slot precedes duplicate key");
434
435 assert_eq!(
436 err,
437 DeclarationSnapshotError::DuplicateSlot(
438 AllocationSlotDescriptor::memory_manager(100).unwrap()
439 )
440 );
441 }
442 }
443}