1use crate::{
2 constants::DIAGNOSTIC_STRING_MAX_BYTES,
3 key::{StableKey, StableKeyError},
4 schema::{SchemaMetadata, SchemaMetadataError},
5 slot::{AllocationSlotDescriptor, MemoryManagerSlotError},
6 validation::Validate,
7};
8use serde::{Deserialize, Serialize};
9use std::collections::BTreeSet;
10
11#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
23#[serde(deny_unknown_fields)]
24pub struct AllocationDeclaration {
25 pub(crate) stable_key: StableKey,
27 pub(crate) slot: AllocationSlotDescriptor,
29 #[serde(deserialize_with = "crate::cbor::deserialize_present_option")]
31 pub(crate) label: Option<String>,
32 pub(crate) schema: SchemaMetadata,
34}
35
36impl AllocationDeclaration {
37 pub fn new(
39 stable_key: impl AsRef<str>,
40 slot: AllocationSlotDescriptor,
41 label: Option<String>,
42 schema: SchemaMetadata,
43 ) -> Result<Self, DeclarationSnapshotError> {
44 let stable_key = StableKey::parse(stable_key).map_err(DeclarationSnapshotError::Key)?;
45 slot.validate()
46 .map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
47 validate_label(label.as_deref())?;
48 schema
49 .validate()
50 .map_err(DeclarationSnapshotError::SchemaMetadata)?;
51 Ok(Self {
52 stable_key,
53 slot,
54 label,
55 schema,
56 })
57 }
58
59 pub fn memory_manager(
61 stable_key: impl AsRef<str>,
62 id: u8,
63 label: impl Into<String>,
64 ) -> Result<Self, DeclarationSnapshotError> {
65 Self::memory_manager_with_schema(stable_key, id, label, SchemaMetadata::default())
66 }
67
68 pub fn memory_manager_unlabeled(
70 stable_key: impl AsRef<str>,
71 id: u8,
72 ) -> Result<Self, DeclarationSnapshotError> {
73 Self::memory_manager_unlabeled_with_schema(stable_key, id, SchemaMetadata::default())
74 }
75
76 pub fn memory_manager_with_schema(
78 stable_key: impl AsRef<str>,
79 id: u8,
80 label: impl Into<String>,
81 schema: SchemaMetadata,
82 ) -> Result<Self, DeclarationSnapshotError> {
83 let slot = AllocationSlotDescriptor::memory_manager(id)
84 .map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
85 Self::new(stable_key, slot, Some(label.into()), schema)
86 }
87
88 pub fn memory_manager_unlabeled_with_schema(
90 stable_key: impl AsRef<str>,
91 id: u8,
92 schema: SchemaMetadata,
93 ) -> Result<Self, DeclarationSnapshotError> {
94 let slot = AllocationSlotDescriptor::memory_manager(id)
95 .map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
96 Self::new(stable_key, slot, None, schema)
97 }
98
99 #[must_use]
101 pub const fn stable_key(&self) -> &StableKey {
102 &self.stable_key
103 }
104
105 #[must_use]
107 pub const fn slot(&self) -> &AllocationSlotDescriptor {
108 &self.slot
109 }
110
111 #[must_use]
113 pub fn label(&self) -> Option<&str> {
114 self.label.as_deref()
115 }
116
117 #[must_use]
119 pub const fn schema(&self) -> &SchemaMetadata {
120 &self.schema
121 }
122
123 pub fn validate(&self) -> Result<(), DeclarationSnapshotError> {
125 self.stable_key
126 .validate()
127 .map_err(DeclarationSnapshotError::Key)?;
128 self.slot
129 .validate()
130 .map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
131 validate_label(self.label.as_deref())?;
132 self.schema
133 .validate()
134 .map_err(DeclarationSnapshotError::SchemaMetadata)
135 }
136}
137
138#[derive(Clone, Debug, Default)]
147pub struct DeclarationCollector {
148 declarations: Vec<AllocationDeclaration>,
149}
150
151impl DeclarationCollector {
152 #[must_use]
154 pub const fn new() -> Self {
155 Self {
156 declarations: Vec::new(),
157 }
158 }
159
160 pub fn push(&mut self, declaration: AllocationDeclaration) {
162 self.declarations.push(declaration);
163 }
164
165 pub fn declare(&mut self, declaration: AllocationDeclaration) -> &mut Self {
167 self.push(declaration);
168 self
169 }
170
171 #[must_use]
173 pub fn with_declaration(mut self, declaration: AllocationDeclaration) -> Self {
174 self.push(declaration);
175 self
176 }
177
178 pub fn declare_memory_manager(
180 &mut self,
181 stable_key: impl AsRef<str>,
182 id: u8,
183 label: impl Into<String>,
184 ) -> Result<&mut Self, DeclarationSnapshotError> {
185 self.declare_memory_manager_with_schema(stable_key, id, label, SchemaMetadata::default())
186 }
187
188 pub fn declare_memory_manager_unlabeled(
190 &mut self,
191 stable_key: impl AsRef<str>,
192 id: u8,
193 ) -> Result<&mut Self, DeclarationSnapshotError> {
194 self.declare_memory_manager_unlabeled_with_schema(stable_key, id, SchemaMetadata::default())
195 }
196
197 pub fn declare_memory_manager_with_schema(
199 &mut self,
200 stable_key: impl AsRef<str>,
201 id: u8,
202 label: impl Into<String>,
203 schema: SchemaMetadata,
204 ) -> Result<&mut Self, DeclarationSnapshotError> {
205 self.push(AllocationDeclaration::memory_manager_with_schema(
206 stable_key, id, label, schema,
207 )?);
208 Ok(self)
209 }
210
211 pub fn declare_memory_manager_unlabeled_with_schema(
213 &mut self,
214 stable_key: impl AsRef<str>,
215 id: u8,
216 schema: SchemaMetadata,
217 ) -> Result<&mut Self, DeclarationSnapshotError> {
218 self.push(AllocationDeclaration::memory_manager_unlabeled_with_schema(
219 stable_key, id, schema,
220 )?);
221 Ok(self)
222 }
223
224 pub fn with_memory_manager(
226 mut self,
227 stable_key: impl AsRef<str>,
228 id: u8,
229 label: impl Into<String>,
230 ) -> Result<Self, DeclarationSnapshotError> {
231 self.declare_memory_manager(stable_key, id, label)?;
232 Ok(self)
233 }
234
235 pub fn with_memory_manager_unlabeled(
237 mut self,
238 stable_key: impl AsRef<str>,
239 id: u8,
240 ) -> Result<Self, DeclarationSnapshotError> {
241 self.declare_memory_manager_unlabeled(stable_key, id)?;
242 Ok(self)
243 }
244
245 pub fn with_memory_manager_schema(
247 mut self,
248 stable_key: impl AsRef<str>,
249 id: u8,
250 label: impl Into<String>,
251 schema: SchemaMetadata,
252 ) -> Result<Self, DeclarationSnapshotError> {
253 self.declare_memory_manager_with_schema(stable_key, id, label, schema)?;
254 Ok(self)
255 }
256
257 pub fn with_memory_manager_unlabeled_schema(
259 mut self,
260 stable_key: impl AsRef<str>,
261 id: u8,
262 schema: SchemaMetadata,
263 ) -> Result<Self, DeclarationSnapshotError> {
264 self.declare_memory_manager_unlabeled_with_schema(stable_key, id, schema)?;
265 Ok(self)
266 }
267
268 pub fn seal(self) -> Result<DeclarationSnapshot, DeclarationSnapshotError> {
270 DeclarationSnapshot::new(self.declarations)
271 }
272}
273
274#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
285#[serde(deny_unknown_fields)]
286pub struct DeclarationSnapshot {
287 declarations: Vec<AllocationDeclaration>,
289 #[serde(deserialize_with = "crate::cbor::deserialize_present_option")]
291 runtime_fingerprint: Option<String>,
292}
293
294impl DeclarationSnapshot {
295 pub fn new(declarations: Vec<AllocationDeclaration>) -> Result<Self, DeclarationSnapshotError> {
297 validate_declarations(&declarations)?;
298 reject_duplicates(&declarations)?;
299 Ok(Self {
300 declarations,
301 runtime_fingerprint: None,
302 })
303 }
304
305 pub fn with_runtime_fingerprint(
307 mut self,
308 fingerprint: impl Into<String>,
309 ) -> Result<Self, DeclarationSnapshotError> {
310 let fingerprint = fingerprint.into();
311 validate_runtime_fingerprint(Some(&fingerprint))?;
312 self.runtime_fingerprint = Some(fingerprint);
313 Ok(self)
314 }
315
316 #[must_use]
318 pub const fn is_empty(&self) -> bool {
319 self.declarations.is_empty()
320 }
321
322 #[must_use]
324 pub const fn len(&self) -> usize {
325 self.declarations.len()
326 }
327
328 #[must_use]
330 pub fn declarations(&self) -> &[AllocationDeclaration] {
331 &self.declarations
332 }
333
334 #[must_use]
336 pub fn runtime_fingerprint(&self) -> Option<&str> {
337 self.runtime_fingerprint.as_deref()
338 }
339
340 pub fn validate(&self) -> Result<(), DeclarationSnapshotError> {
342 validate_declarations(&self.declarations)?;
343 reject_duplicates(&self.declarations)?;
344 validate_runtime_fingerprint(self.runtime_fingerprint.as_deref())
345 }
346
347 pub(crate) fn into_parts(self) -> (Vec<AllocationDeclaration>, Option<String>) {
348 (self.declarations, self.runtime_fingerprint)
349 }
350}
351
352#[non_exhaustive]
357#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
358pub enum DeclarationSnapshotError {
359 #[error("at most 255 allocation declarations are supported")]
360 TooManyDeclarations,
361 #[error(transparent)]
363 Key(StableKeyError),
364 #[error(transparent)]
366 MemoryManagerSlot(MemoryManagerSlotError),
367 #[error(transparent)]
369 SchemaMetadata(SchemaMetadataError),
370 #[error("stable key '{0}' is declared more than once")]
372 DuplicateStableKey(StableKey),
373 #[error("allocation slot '{0:?}' is declared more than once")]
375 DuplicateSlot(AllocationSlotDescriptor),
376 #[error("allocation declaration label must not be empty when present")]
378 EmptyLabel,
379 #[error("allocation declaration label must be at most 256 bytes")]
381 LabelTooLong,
382 #[error("allocation declaration label must be ASCII")]
384 NonAsciiLabel,
385 #[error("allocation declaration label must not contain ASCII control characters")]
387 ControlCharacterLabel,
388 #[error("runtime_fingerprint must not be empty when present")]
390 EmptyRuntimeFingerprint,
391 #[error("runtime_fingerprint must be at most 256 bytes")]
393 RuntimeFingerprintTooLong,
394 #[error("runtime_fingerprint must be ASCII")]
396 NonAsciiRuntimeFingerprint,
397 #[error("runtime_fingerprint must not contain ASCII control characters")]
399 ControlCharacterRuntimeFingerprint,
400}
401
402fn validate_label(label: Option<&str>) -> Result<(), DeclarationSnapshotError> {
403 let Some(label) = label else {
404 return Ok(());
405 };
406 if label.is_empty() {
407 return Err(DeclarationSnapshotError::EmptyLabel);
408 }
409 if label.len() > DIAGNOSTIC_STRING_MAX_BYTES {
410 return Err(DeclarationSnapshotError::LabelTooLong);
411 }
412 if !label.is_ascii() {
413 return Err(DeclarationSnapshotError::NonAsciiLabel);
414 }
415 if label.bytes().any(|byte| byte.is_ascii_control()) {
416 return Err(DeclarationSnapshotError::ControlCharacterLabel);
417 }
418 Ok(())
419}
420
421fn validate_declarations(
422 declarations: &[AllocationDeclaration],
423) -> Result<(), DeclarationSnapshotError> {
424 if declarations.len() > 255 {
425 return Err(DeclarationSnapshotError::TooManyDeclarations);
426 }
427 for declaration in declarations {
428 declaration.validate()?;
429 }
430 Ok(())
431}
432
433pub fn validate_runtime_fingerprint(
434 fingerprint: Option<&str>,
435) -> Result<(), DeclarationSnapshotError> {
436 let Some(fingerprint) = fingerprint else {
437 return Ok(());
438 };
439 if fingerprint.is_empty() {
440 return Err(DeclarationSnapshotError::EmptyRuntimeFingerprint);
441 }
442 if fingerprint.len() > DIAGNOSTIC_STRING_MAX_BYTES {
443 return Err(DeclarationSnapshotError::RuntimeFingerprintTooLong);
444 }
445 if !fingerprint.is_ascii() {
446 return Err(DeclarationSnapshotError::NonAsciiRuntimeFingerprint);
447 }
448 if fingerprint.bytes().any(|byte| byte.is_ascii_control()) {
449 return Err(DeclarationSnapshotError::ControlCharacterRuntimeFingerprint);
450 }
451 Ok(())
452}
453
454fn reject_duplicates(
455 declarations: &[AllocationDeclaration],
456) -> Result<(), DeclarationSnapshotError> {
457 let mut keys = BTreeSet::new();
458 let mut slots = BTreeSet::new();
459
460 for declaration in declarations {
461 if !slots.insert(declaration.slot.clone()) {
462 return Err(DeclarationSnapshotError::DuplicateSlot(
463 declaration.slot.clone(),
464 ));
465 }
466 if !keys.insert(declaration.stable_key.clone()) {
467 return Err(DeclarationSnapshotError::DuplicateStableKey(
468 declaration.stable_key.clone(),
469 ));
470 }
471 }
472
473 Ok(())
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479 use crate::slot::AllocationSlotDescriptor;
480
481 fn declaration(key: &str, id: u8) -> AllocationDeclaration {
482 AllocationDeclaration::new(
483 key,
484 AllocationSlotDescriptor::memory_manager(id).expect("usable slot"),
485 None,
486 SchemaMetadata::default(),
487 )
488 .expect("declaration")
489 }
490
491 #[test]
492 fn declaration_rejects_unbounded_label_metadata() {
493 let err = AllocationDeclaration::new(
494 "app.users.v1",
495 AllocationSlotDescriptor::memory_manager(100).expect("usable slot"),
496 Some("x".repeat(257)),
497 SchemaMetadata::default(),
498 )
499 .expect_err("label too long");
500
501 assert_eq!(err, DeclarationSnapshotError::LabelTooLong);
502 }
503
504 #[test]
505 fn memory_manager_declaration_constructor_builds_common_declaration() {
506 let declaration = AllocationDeclaration::memory_manager("app.orders.v1", 100, "orders")
507 .expect("declaration");
508
509 assert_eq!(declaration.stable_key.as_str(), "app.orders.v1");
510 assert_eq!(
511 declaration.slot,
512 AllocationSlotDescriptor::memory_manager(100).expect("usable slot")
513 );
514 assert_eq!(declaration.label.as_deref(), Some("orders"));
515 assert_eq!(declaration.schema, SchemaMetadata::default());
516 }
517
518 #[test]
519 fn memory_manager_declaration_constructor_rejects_invalid_slot() {
520 let err = AllocationDeclaration::memory_manager("app.orders.v1", u8::MAX, "orders")
521 .expect_err("sentinel must fail");
522
523 assert!(matches!(
524 err,
525 DeclarationSnapshotError::MemoryManagerSlot(_)
526 ));
527 }
528
529 #[test]
530 fn snapshot_rejects_decoded_invalid_memory_manager_slot() {
531 let mut declaration = declaration("app.orders.v1", 100);
532 declaration.slot =
533 AllocationSlotDescriptor::memory_manager_unchecked(crate::MEMORY_MANAGER_INVALID_ID);
534
535 let err = DeclarationSnapshot::new(vec![declaration]).expect_err("snapshot must fail");
536
537 assert!(matches!(
538 err,
539 DeclarationSnapshotError::MemoryManagerSlot(
540 MemoryManagerSlotError::InvalidMemoryManagerId { id }
541 ) if id == crate::MEMORY_MANAGER_INVALID_ID
542 ));
543 }
544
545 #[test]
546 fn declaration_collector_declares_memory_manager_allocations() {
547 let mut declarations = DeclarationCollector::new();
548 declarations
549 .declare_memory_manager("app.orders.v1", 100, "orders")
550 .expect("orders declaration")
551 .declare_memory_manager_unlabeled("app.users.v1", 101)
552 .expect("users declaration");
553
554 let snapshot = declarations.seal().expect("snapshot");
555
556 assert_eq!(snapshot.len(), 2);
557 assert_eq!(
558 snapshot.declarations()[0].slot,
559 AllocationSlotDescriptor::memory_manager(100).expect("usable slot")
560 );
561 assert_eq!(snapshot.declarations()[0].label.as_deref(), Some("orders"));
562 assert_eq!(snapshot.declarations()[1].label, None);
563 }
564
565 #[test]
566 fn declaration_collector_builder_declares_memory_manager_allocations() {
567 let snapshot = DeclarationCollector::new()
568 .with_memory_manager("app.orders.v1", 100, "orders")
569 .expect("orders declaration")
570 .with_memory_manager_unlabeled("app.users.v1", 101)
571 .expect("users declaration")
572 .seal()
573 .expect("snapshot");
574
575 assert_eq!(snapshot.len(), 2);
576 }
577
578 #[test]
579 fn snapshot_rejects_unbounded_runtime_fingerprint() {
580 let snapshot =
581 DeclarationSnapshot::new(vec![declaration("app.users.v1", 100)]).expect("snapshot");
582
583 let err = snapshot
584 .with_runtime_fingerprint("x".repeat(257))
585 .expect_err("fingerprint too long");
586
587 assert_eq!(err, DeclarationSnapshotError::RuntimeFingerprintTooLong);
588 }
589
590 #[test]
591 fn rejects_duplicate_keys() {
592 let err = DeclarationSnapshot::new(vec![
593 declaration("app.users.v1", 100),
594 declaration("app.users.v1", 101),
595 ])
596 .expect_err("duplicate key");
597
598 assert!(matches!(
599 err,
600 DeclarationSnapshotError::DuplicateStableKey(_)
601 ));
602 }
603
604 #[test]
605 fn rejects_duplicate_slots() {
606 let err = DeclarationSnapshot::new(vec![
607 declaration("app.users.v1", 100),
608 declaration("app.orders.v1", 100),
609 ])
610 .expect_err("duplicate slot");
611
612 assert!(matches!(err, DeclarationSnapshotError::DuplicateSlot(_)));
613 }
614}