1use crate::{
2 key::{StableKey, StableKeyError},
3 schema::{SchemaMetadata, SchemaMetadataError},
4 slot::{AllocationSlotDescriptor, MemoryManagerSlotError},
5 text::{DiagnosticTextError, validate_diagnostic_text},
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 validate_diagnostic_text(label).map_err(|error| match error {
407 DiagnosticTextError::Empty => DeclarationSnapshotError::EmptyLabel,
408 DiagnosticTextError::TooLong => DeclarationSnapshotError::LabelTooLong,
409 DiagnosticTextError::NonAscii => DeclarationSnapshotError::NonAsciiLabel,
410 DiagnosticTextError::ControlCharacter => DeclarationSnapshotError::ControlCharacterLabel,
411 })
412}
413
414fn validate_declarations(
415 declarations: &[AllocationDeclaration],
416) -> Result<(), DeclarationSnapshotError> {
417 if declarations.len() > 255 {
418 return Err(DeclarationSnapshotError::TooManyDeclarations);
419 }
420 for declaration in declarations {
421 declaration.validate()?;
422 }
423 Ok(())
424}
425
426pub fn validate_runtime_fingerprint(
427 fingerprint: Option<&str>,
428) -> Result<(), DeclarationSnapshotError> {
429 let Some(fingerprint) = fingerprint else {
430 return Ok(());
431 };
432 validate_diagnostic_text(fingerprint).map_err(|error| match error {
433 DiagnosticTextError::Empty => DeclarationSnapshotError::EmptyRuntimeFingerprint,
434 DiagnosticTextError::TooLong => DeclarationSnapshotError::RuntimeFingerprintTooLong,
435 DiagnosticTextError::NonAscii => DeclarationSnapshotError::NonAsciiRuntimeFingerprint,
436 DiagnosticTextError::ControlCharacter => {
437 DeclarationSnapshotError::ControlCharacterRuntimeFingerprint
438 }
439 })
440}
441
442fn reject_duplicates(
443 declarations: &[AllocationDeclaration],
444) -> Result<(), DeclarationSnapshotError> {
445 let mut keys = BTreeSet::new();
446 let mut slots = BTreeSet::new();
447
448 for declaration in declarations {
449 if !slots.insert(&declaration.slot) {
450 return Err(DeclarationSnapshotError::DuplicateSlot(
451 declaration.slot.clone(),
452 ));
453 }
454 if !keys.insert(&declaration.stable_key) {
455 return Err(DeclarationSnapshotError::DuplicateStableKey(
456 declaration.stable_key.clone(),
457 ));
458 }
459 }
460
461 Ok(())
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467 use crate::slot::AllocationSlotDescriptor;
468
469 fn declaration(key: &str, id: u8) -> AllocationDeclaration {
470 AllocationDeclaration::new(
471 key,
472 AllocationSlotDescriptor::memory_manager(id).expect("usable slot"),
473 None,
474 SchemaMetadata::default(),
475 )
476 .expect("declaration")
477 }
478
479 #[test]
480 fn declaration_rejects_unbounded_label_metadata() {
481 let err = AllocationDeclaration::new(
482 "app.users.v1",
483 AllocationSlotDescriptor::memory_manager(100).expect("usable slot"),
484 Some("x".repeat(257)),
485 SchemaMetadata::default(),
486 )
487 .expect_err("label too long");
488
489 assert_eq!(err, DeclarationSnapshotError::LabelTooLong);
490 }
491
492 #[test]
493 fn memory_manager_declaration_constructor_builds_common_declaration() {
494 let declaration = AllocationDeclaration::memory_manager("app.orders.v1", 100, "orders")
495 .expect("declaration");
496
497 assert_eq!(declaration.stable_key.as_str(), "app.orders.v1");
498 assert_eq!(
499 declaration.slot,
500 AllocationSlotDescriptor::memory_manager(100).expect("usable slot")
501 );
502 assert_eq!(declaration.label.as_deref(), Some("orders"));
503 assert_eq!(declaration.schema, SchemaMetadata::default());
504 }
505
506 #[test]
507 fn memory_manager_declaration_constructor_rejects_invalid_slot() {
508 let err = AllocationDeclaration::memory_manager("app.orders.v1", u8::MAX, "orders")
509 .expect_err("sentinel must fail");
510
511 assert!(matches!(
512 err,
513 DeclarationSnapshotError::MemoryManagerSlot(_)
514 ));
515 }
516
517 #[test]
518 fn snapshot_rejects_decoded_invalid_memory_manager_slot() {
519 let mut declaration = declaration("app.orders.v1", 100);
520 declaration.slot =
521 AllocationSlotDescriptor::memory_manager_unchecked(crate::MEMORY_MANAGER_INVALID_ID);
522
523 let err = DeclarationSnapshot::new(vec![declaration]).expect_err("snapshot must fail");
524
525 assert!(matches!(
526 err,
527 DeclarationSnapshotError::MemoryManagerSlot(
528 MemoryManagerSlotError::InvalidMemoryManagerId { id }
529 ) if id == crate::MEMORY_MANAGER_INVALID_ID
530 ));
531 }
532
533 #[test]
534 fn declaration_collector_declares_memory_manager_allocations() {
535 let mut declarations = DeclarationCollector::new();
536 declarations
537 .declare_memory_manager("app.orders.v1", 100, "orders")
538 .expect("orders declaration")
539 .declare_memory_manager_unlabeled("app.users.v1", 101)
540 .expect("users declaration");
541
542 let snapshot = declarations.seal().expect("snapshot");
543
544 assert_eq!(snapshot.len(), 2);
545 assert_eq!(
546 snapshot.declarations()[0].slot,
547 AllocationSlotDescriptor::memory_manager(100).expect("usable slot")
548 );
549 assert_eq!(snapshot.declarations()[0].label.as_deref(), Some("orders"));
550 assert_eq!(snapshot.declarations()[1].label, None);
551 }
552
553 #[test]
554 fn declaration_collector_builder_declares_memory_manager_allocations() {
555 let snapshot = DeclarationCollector::new()
556 .with_memory_manager("app.orders.v1", 100, "orders")
557 .expect("orders declaration")
558 .with_memory_manager_unlabeled("app.users.v1", 101)
559 .expect("users declaration")
560 .seal()
561 .expect("snapshot");
562
563 assert_eq!(snapshot.len(), 2);
564 }
565
566 #[test]
567 fn snapshot_rejects_unbounded_runtime_fingerprint() {
568 let snapshot =
569 DeclarationSnapshot::new(vec![declaration("app.users.v1", 100)]).expect("snapshot");
570
571 let err = snapshot
572 .with_runtime_fingerprint("x".repeat(257))
573 .expect_err("fingerprint too long");
574
575 assert_eq!(err, DeclarationSnapshotError::RuntimeFingerprintTooLong);
576 }
577
578 #[test]
579 fn rejects_duplicate_keys() {
580 let err = DeclarationSnapshot::new(vec![
581 declaration("app.users.v1", 100),
582 declaration("app.users.v1", 101),
583 ])
584 .expect_err("duplicate key");
585
586 assert_eq!(
587 err,
588 DeclarationSnapshotError::DuplicateStableKey(StableKey::parse("app.users.v1").unwrap())
589 );
590 }
591
592 #[test]
593 fn rejects_duplicate_slots() {
594 let err = DeclarationSnapshot::new(vec![
595 declaration("app.users.v1", 100),
596 declaration("app.orders.v1", 100),
597 ])
598 .expect_err("duplicate slot");
599
600 assert_eq!(
601 err,
602 DeclarationSnapshotError::DuplicateSlot(
603 AllocationSlotDescriptor::memory_manager(100).unwrap()
604 )
605 );
606 }
607}