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)]
21#[serde(deny_unknown_fields)]
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::MemoryManagerSlot)?;
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::MemoryManagerSlot)?;
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)]
280#[serde(deny_unknown_fields)]
281pub struct DeclarationSnapshot {
282 declarations: Vec<AllocationDeclaration>,
284 runtime_fingerprint: Option<String>,
286}
287
288impl DeclarationSnapshot {
289 pub fn new(declarations: Vec<AllocationDeclaration>) -> Result<Self, DeclarationSnapshotError> {
291 validate_declarations(&declarations)?;
292 reject_duplicates(&declarations)?;
293 Ok(Self {
294 declarations,
295 runtime_fingerprint: None,
296 })
297 }
298
299 pub fn with_runtime_fingerprint(
301 mut self,
302 fingerprint: impl Into<String>,
303 ) -> Result<Self, DeclarationSnapshotError> {
304 let fingerprint = fingerprint.into();
305 validate_runtime_fingerprint(Some(&fingerprint))?;
306 self.runtime_fingerprint = Some(fingerprint);
307 Ok(self)
308 }
309
310 #[must_use]
312 pub fn is_empty(&self) -> bool {
313 self.declarations.is_empty()
314 }
315
316 #[must_use]
318 pub fn len(&self) -> usize {
319 self.declarations.len()
320 }
321
322 #[must_use]
324 pub fn declarations(&self) -> &[AllocationDeclaration] {
325 &self.declarations
326 }
327
328 #[must_use]
330 pub fn runtime_fingerprint(&self) -> Option<&str> {
331 self.runtime_fingerprint.as_deref()
332 }
333
334 pub fn validate(&self) -> Result<(), DeclarationSnapshotError> {
336 validate_declarations(&self.declarations)?;
337 reject_duplicates(&self.declarations)?;
338 validate_runtime_fingerprint(self.runtime_fingerprint.as_deref())
339 }
340
341 pub(crate) fn into_parts(self) -> (Vec<AllocationDeclaration>, Option<String>) {
342 (self.declarations, self.runtime_fingerprint)
343 }
344}
345
346#[non_exhaustive]
351#[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 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}