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