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