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