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 fn is_empty(&self) -> bool {
319 self.declarations.is_empty()
320 }
321
322 #[must_use]
324 pub 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(transparent)]
361 Key(StableKeyError),
362 #[error(transparent)]
364 MemoryManagerSlot(MemoryManagerSlotError),
365 #[error(transparent)]
367 SchemaMetadata(SchemaMetadataError),
368 #[error("stable key '{0}' is declared more than once")]
370 DuplicateStableKey(StableKey),
371 #[error("allocation slot '{0:?}' is declared more than once")]
373 DuplicateSlot(AllocationSlotDescriptor),
374 #[error("allocation declaration label must not be empty when present")]
376 EmptyLabel,
377 #[error("allocation declaration label must be at most 256 bytes")]
379 LabelTooLong,
380 #[error("allocation declaration label must be ASCII")]
382 NonAsciiLabel,
383 #[error("allocation declaration label must not contain ASCII control characters")]
385 ControlCharacterLabel,
386 #[error("runtime_fingerprint must not be empty when present")]
388 EmptyRuntimeFingerprint,
389 #[error("runtime_fingerprint must be at most 256 bytes")]
391 RuntimeFingerprintTooLong,
392 #[error("runtime_fingerprint must be ASCII")]
394 NonAsciiRuntimeFingerprint,
395 #[error("runtime_fingerprint must not contain ASCII control characters")]
397 ControlCharacterRuntimeFingerprint,
398}
399
400fn validate_label(label: Option<&str>) -> Result<(), DeclarationSnapshotError> {
401 let Some(label) = label else {
402 return Ok(());
403 };
404 if label.is_empty() {
405 return Err(DeclarationSnapshotError::EmptyLabel);
406 }
407 if label.len() > DIAGNOSTIC_STRING_MAX_BYTES {
408 return Err(DeclarationSnapshotError::LabelTooLong);
409 }
410 if !label.is_ascii() {
411 return Err(DeclarationSnapshotError::NonAsciiLabel);
412 }
413 if label.bytes().any(|byte| byte.is_ascii_control()) {
414 return Err(DeclarationSnapshotError::ControlCharacterLabel);
415 }
416 Ok(())
417}
418
419fn validate_declarations(
420 declarations: &[AllocationDeclaration],
421) -> Result<(), DeclarationSnapshotError> {
422 for declaration in declarations {
423 declaration.validate()?;
424 }
425 Ok(())
426}
427
428pub fn validate_runtime_fingerprint(
429 fingerprint: Option<&str>,
430) -> Result<(), DeclarationSnapshotError> {
431 let Some(fingerprint) = fingerprint else {
432 return Ok(());
433 };
434 if fingerprint.is_empty() {
435 return Err(DeclarationSnapshotError::EmptyRuntimeFingerprint);
436 }
437 if fingerprint.len() > DIAGNOSTIC_STRING_MAX_BYTES {
438 return Err(DeclarationSnapshotError::RuntimeFingerprintTooLong);
439 }
440 if !fingerprint.is_ascii() {
441 return Err(DeclarationSnapshotError::NonAsciiRuntimeFingerprint);
442 }
443 if fingerprint.bytes().any(|byte| byte.is_ascii_control()) {
444 return Err(DeclarationSnapshotError::ControlCharacterRuntimeFingerprint);
445 }
446 Ok(())
447}
448
449fn reject_duplicates(
450 declarations: &[AllocationDeclaration],
451) -> Result<(), DeclarationSnapshotError> {
452 let mut keys = BTreeSet::new();
453 let mut slots = BTreeSet::new();
454
455 for declaration in declarations {
456 if !slots.insert(declaration.slot.clone()) {
457 return Err(DeclarationSnapshotError::DuplicateSlot(
458 declaration.slot.clone(),
459 ));
460 }
461 if !keys.insert(declaration.stable_key.clone()) {
462 return Err(DeclarationSnapshotError::DuplicateStableKey(
463 declaration.stable_key.clone(),
464 ));
465 }
466 }
467
468 Ok(())
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474 use crate::slot::AllocationSlotDescriptor;
475
476 fn declaration(key: &str, id: u8) -> AllocationDeclaration {
477 AllocationDeclaration::new(
478 key,
479 AllocationSlotDescriptor::memory_manager(id).expect("usable slot"),
480 None,
481 SchemaMetadata::default(),
482 )
483 .expect("declaration")
484 }
485
486 #[test]
487 fn declaration_rejects_unbounded_label_metadata() {
488 let err = AllocationDeclaration::new(
489 "app.users.v1",
490 AllocationSlotDescriptor::memory_manager(100).expect("usable slot"),
491 Some("x".repeat(257)),
492 SchemaMetadata::default(),
493 )
494 .expect_err("label too long");
495
496 assert_eq!(err, DeclarationSnapshotError::LabelTooLong);
497 }
498
499 #[test]
500 fn memory_manager_declaration_constructor_builds_common_declaration() {
501 let declaration = AllocationDeclaration::memory_manager("app.orders.v1", 100, "orders")
502 .expect("declaration");
503
504 assert_eq!(declaration.stable_key.as_str(), "app.orders.v1");
505 assert_eq!(
506 declaration.slot,
507 AllocationSlotDescriptor::memory_manager(100).expect("usable slot")
508 );
509 assert_eq!(declaration.label.as_deref(), Some("orders"));
510 assert_eq!(declaration.schema, SchemaMetadata::default());
511 }
512
513 #[test]
514 fn memory_manager_declaration_constructor_rejects_invalid_slot() {
515 let err = AllocationDeclaration::memory_manager("app.orders.v1", u8::MAX, "orders")
516 .expect_err("sentinel must fail");
517
518 assert!(matches!(
519 err,
520 DeclarationSnapshotError::MemoryManagerSlot(_)
521 ));
522 }
523
524 #[test]
525 fn snapshot_rejects_decoded_invalid_memory_manager_slot() {
526 let mut declaration = declaration("app.orders.v1", 100);
527 declaration.slot =
528 AllocationSlotDescriptor::memory_manager_unchecked(crate::MEMORY_MANAGER_INVALID_ID);
529
530 let err = DeclarationSnapshot::new(vec![declaration]).expect_err("snapshot must fail");
531
532 assert!(matches!(
533 err,
534 DeclarationSnapshotError::MemoryManagerSlot(
535 MemoryManagerSlotError::InvalidMemoryManagerId { id }
536 ) if id == crate::MEMORY_MANAGER_INVALID_ID
537 ));
538 }
539
540 #[test]
541 fn declaration_collector_declares_memory_manager_allocations() {
542 let mut declarations = DeclarationCollector::new();
543 declarations
544 .declare_memory_manager("app.orders.v1", 100, "orders")
545 .expect("orders declaration")
546 .declare_memory_manager_unlabeled("app.users.v1", 101)
547 .expect("users declaration");
548
549 let snapshot = declarations.seal().expect("snapshot");
550
551 assert_eq!(snapshot.len(), 2);
552 assert_eq!(
553 snapshot.declarations()[0].slot,
554 AllocationSlotDescriptor::memory_manager(100).expect("usable slot")
555 );
556 assert_eq!(snapshot.declarations()[0].label.as_deref(), Some("orders"));
557 assert_eq!(snapshot.declarations()[1].label, None);
558 }
559
560 #[test]
561 fn declaration_collector_builder_declares_memory_manager_allocations() {
562 let snapshot = DeclarationCollector::new()
563 .with_memory_manager("app.orders.v1", 100, "orders")
564 .expect("orders declaration")
565 .with_memory_manager_unlabeled("app.users.v1", 101)
566 .expect("users declaration")
567 .seal()
568 .expect("snapshot");
569
570 assert_eq!(snapshot.len(), 2);
571 }
572
573 #[test]
574 fn snapshot_rejects_unbounded_runtime_fingerprint() {
575 let snapshot =
576 DeclarationSnapshot::new(vec![declaration("app.users.v1", 100)]).expect("snapshot");
577
578 let err = snapshot
579 .with_runtime_fingerprint("x".repeat(257))
580 .expect_err("fingerprint too long");
581
582 assert_eq!(err, DeclarationSnapshotError::RuntimeFingerprintTooLong);
583 }
584
585 #[test]
586 fn rejects_duplicate_keys() {
587 let err = DeclarationSnapshot::new(vec![
588 declaration("app.users.v1", 100),
589 declaration("app.users.v1", 101),
590 ])
591 .expect_err("duplicate key");
592
593 assert!(matches!(
594 err,
595 DeclarationSnapshotError::DuplicateStableKey(_)
596 ));
597 }
598
599 #[test]
600 fn rejects_duplicate_slots() {
601 let err = DeclarationSnapshot::new(vec![
602 declaration("app.users.v1", 100),
603 declaration("app.orders.v1", 100),
604 ])
605 .expect_err("duplicate slot");
606
607 assert!(matches!(err, DeclarationSnapshotError::DuplicateSlot(_)));
608 }
609}