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#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
351pub enum DeclarationSnapshotError {
352 #[error(transparent)]
354 Key(StableKeyError),
355 #[error(transparent)]
357 MemoryManagerSlot(MemoryManagerSlotError),
358 #[error(transparent)]
360 SchemaMetadata(SchemaMetadataError),
361 #[error("stable key '{0}' is declared more than once")]
363 DuplicateStableKey(StableKey),
364 #[error("allocation slot '{0:?}' is declared more than once")]
366 DuplicateSlot(AllocationSlotDescriptor),
367 #[error("allocation declaration label must not be empty when present")]
369 EmptyLabel,
370 #[error("allocation declaration label must be at most 256 bytes")]
372 LabelTooLong,
373 #[error("allocation declaration label must be ASCII")]
375 NonAsciiLabel,
376 #[error("allocation declaration label must not contain ASCII control characters")]
378 ControlCharacterLabel,
379 #[error("runtime_fingerprint must not be empty when present")]
381 EmptyRuntimeFingerprint,
382 #[error("runtime_fingerprint must be at most 256 bytes")]
384 RuntimeFingerprintTooLong,
385 #[error("runtime_fingerprint must be ASCII")]
387 NonAsciiRuntimeFingerprint,
388 #[error("runtime_fingerprint must not contain ASCII control characters")]
390 ControlCharacterRuntimeFingerprint,
391}
392
393fn validate_label(label: Option<&str>) -> Result<(), DeclarationSnapshotError> {
394 let Some(label) = label else {
395 return Ok(());
396 };
397 if label.is_empty() {
398 return Err(DeclarationSnapshotError::EmptyLabel);
399 }
400 if label.len() > DIAGNOSTIC_STRING_MAX_BYTES {
401 return Err(DeclarationSnapshotError::LabelTooLong);
402 }
403 if !label.is_ascii() {
404 return Err(DeclarationSnapshotError::NonAsciiLabel);
405 }
406 if label.bytes().any(|byte| byte.is_ascii_control()) {
407 return Err(DeclarationSnapshotError::ControlCharacterLabel);
408 }
409 Ok(())
410}
411
412fn validate_declarations(
413 declarations: &[AllocationDeclaration],
414) -> Result<(), DeclarationSnapshotError> {
415 for declaration in declarations {
416 declaration.validate()?;
417 }
418 Ok(())
419}
420
421pub(crate) fn validate_runtime_fingerprint(
422 fingerprint: Option<&str>,
423) -> Result<(), DeclarationSnapshotError> {
424 let Some(fingerprint) = fingerprint else {
425 return Ok(());
426 };
427 if fingerprint.is_empty() {
428 return Err(DeclarationSnapshotError::EmptyRuntimeFingerprint);
429 }
430 if fingerprint.len() > DIAGNOSTIC_STRING_MAX_BYTES {
431 return Err(DeclarationSnapshotError::RuntimeFingerprintTooLong);
432 }
433 if !fingerprint.is_ascii() {
434 return Err(DeclarationSnapshotError::NonAsciiRuntimeFingerprint);
435 }
436 if fingerprint.bytes().any(|byte| byte.is_ascii_control()) {
437 return Err(DeclarationSnapshotError::ControlCharacterRuntimeFingerprint);
438 }
439 Ok(())
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.clone()) {
450 return Err(DeclarationSnapshotError::DuplicateSlot(
451 declaration.slot.clone(),
452 ));
453 }
454 if !keys.insert(declaration.stable_key.clone()) {
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!(matches!(
587 err,
588 DeclarationSnapshotError::DuplicateStableKey(_)
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!(matches!(err, DeclarationSnapshotError::DuplicateSlot(_)));
601 }
602}