1use crate::{
2 key::{StableKey, StableKeyError},
3 schema::{SchemaMetadata, SchemaMetadataError},
4 slot::{MemoryManagerSlot, MemoryManagerSlotError},
5 text::{DiagnosticTextError, validate_diagnostic_text},
6};
7use serde::{Deserialize, Serialize};
8use std::collections::BTreeSet;
9
10#[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: MemoryManagerSlot,
28 #[serde(deserialize_with = "crate::cbor::deserialize_present_option")]
30 pub(crate) label: Option<String>,
31 pub(crate) schema: SchemaMetadata,
33}
34
35impl AllocationDeclaration {
36 pub fn new(
38 stable_key: impl AsRef<str>,
39 slot: MemoryManagerSlot,
40 label: Option<String>,
41 schema: SchemaMetadata,
42 ) -> Result<Self, DeclarationSnapshotError> {
43 let stable_key = StableKey::parse(stable_key).map_err(DeclarationSnapshotError::Key)?;
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 =
81 MemoryManagerSlot::new(id).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 =
92 MemoryManagerSlot::new(id).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) -> &MemoryManagerSlot {
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 validate_label(self.label.as_deref())?;
126 self.schema
127 .validate()
128 .map_err(DeclarationSnapshotError::SchemaMetadata)
129 }
130}
131
132#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
143#[serde(deny_unknown_fields)]
144pub struct DeclarationSnapshot {
145 declarations: Vec<AllocationDeclaration>,
147 #[serde(deserialize_with = "crate::cbor::deserialize_present_option")]
149 runtime_fingerprint: Option<String>,
150}
151
152impl DeclarationSnapshot {
153 pub fn new(declarations: Vec<AllocationDeclaration>) -> Result<Self, DeclarationSnapshotError> {
155 validate_declarations(&declarations)?;
156 reject_duplicates(&declarations)?;
157 Ok(Self {
158 declarations,
159 runtime_fingerprint: None,
160 })
161 }
162
163 pub fn with_runtime_fingerprint(
165 mut self,
166 fingerprint: impl Into<String>,
167 ) -> Result<Self, DeclarationSnapshotError> {
168 let fingerprint = fingerprint.into();
169 validate_runtime_fingerprint(Some(&fingerprint))?;
170 self.runtime_fingerprint = Some(fingerprint);
171 Ok(self)
172 }
173
174 #[must_use]
176 pub const fn is_empty(&self) -> bool {
177 self.declarations.is_empty()
178 }
179
180 #[must_use]
182 pub const fn len(&self) -> usize {
183 self.declarations.len()
184 }
185
186 #[must_use]
188 pub fn declarations(&self) -> &[AllocationDeclaration] {
189 &self.declarations
190 }
191
192 #[must_use]
194 pub fn runtime_fingerprint(&self) -> Option<&str> {
195 self.runtime_fingerprint.as_deref()
196 }
197
198 pub fn validate(&self) -> Result<(), DeclarationSnapshotError> {
200 validate_declarations(&self.declarations)?;
201 reject_duplicates(&self.declarations)?;
202 validate_runtime_fingerprint(self.runtime_fingerprint.as_deref())
203 }
204
205 pub(crate) fn into_parts(self) -> (Vec<AllocationDeclaration>, Option<String>) {
206 (self.declarations, self.runtime_fingerprint)
207 }
208}
209
210#[non_exhaustive]
215#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
216pub enum DeclarationSnapshotError {
217 #[error("at most 255 allocation declarations are supported")]
218 TooManyDeclarations,
219 #[error(transparent)]
221 Key(StableKeyError),
222 #[error(transparent)]
224 MemoryManagerSlot(MemoryManagerSlotError),
225 #[error(transparent)]
227 SchemaMetadata(SchemaMetadataError),
228 #[error("stable key '{0}' is declared more than once")]
230 DuplicateStableKey(StableKey),
231 #[error("allocation slot '{0:?}' is declared more than once")]
233 DuplicateSlot(MemoryManagerSlot),
234 #[error("allocation declaration label must not be empty when present")]
236 EmptyLabel,
237 #[error("allocation declaration label must be at most 256 bytes")]
239 LabelTooLong,
240 #[error("allocation declaration label must be ASCII")]
242 NonAsciiLabel,
243 #[error("allocation declaration label must not contain ASCII control characters")]
245 ControlCharacterLabel,
246 #[error("runtime_fingerprint must not be empty when present")]
248 EmptyRuntimeFingerprint,
249 #[error("runtime_fingerprint must be at most 256 bytes")]
251 RuntimeFingerprintTooLong,
252 #[error("runtime_fingerprint must be ASCII")]
254 NonAsciiRuntimeFingerprint,
255 #[error("runtime_fingerprint must not contain ASCII control characters")]
257 ControlCharacterRuntimeFingerprint,
258}
259
260fn validate_label(label: Option<&str>) -> Result<(), DeclarationSnapshotError> {
261 let Some(label) = label else {
262 return Ok(());
263 };
264 validate_diagnostic_text(label).map_err(|error| match error {
265 DiagnosticTextError::Empty => DeclarationSnapshotError::EmptyLabel,
266 DiagnosticTextError::TooLong => DeclarationSnapshotError::LabelTooLong,
267 DiagnosticTextError::NonAscii => DeclarationSnapshotError::NonAsciiLabel,
268 DiagnosticTextError::ControlCharacter => DeclarationSnapshotError::ControlCharacterLabel,
269 })
270}
271
272fn validate_declarations(
273 declarations: &[AllocationDeclaration],
274) -> Result<(), DeclarationSnapshotError> {
275 if declarations.len() > crate::constants::MAX_ALLOCATIONS {
276 return Err(DeclarationSnapshotError::TooManyDeclarations);
277 }
278 for declaration in declarations {
279 declaration.validate()?;
280 }
281 Ok(())
282}
283
284pub fn validate_runtime_fingerprint(
285 fingerprint: Option<&str>,
286) -> Result<(), DeclarationSnapshotError> {
287 let Some(fingerprint) = fingerprint else {
288 return Ok(());
289 };
290 validate_diagnostic_text(fingerprint).map_err(|error| match error {
291 DiagnosticTextError::Empty => DeclarationSnapshotError::EmptyRuntimeFingerprint,
292 DiagnosticTextError::TooLong => DeclarationSnapshotError::RuntimeFingerprintTooLong,
293 DiagnosticTextError::NonAscii => DeclarationSnapshotError::NonAsciiRuntimeFingerprint,
294 DiagnosticTextError::ControlCharacter => {
295 DeclarationSnapshotError::ControlCharacterRuntimeFingerprint
296 }
297 })
298}
299
300fn reject_duplicates(
301 declarations: &[AllocationDeclaration],
302) -> Result<(), DeclarationSnapshotError> {
303 let mut keys = BTreeSet::new();
304 let mut slots = [false; crate::constants::MAX_ALLOCATIONS];
305
306 for declaration in declarations {
307 let occupied = &mut slots[usize::from(declaration.slot.id())];
308 if *occupied {
309 return Err(DeclarationSnapshotError::DuplicateSlot(
310 declaration.slot.clone(),
311 ));
312 }
313 *occupied = true;
314 if !keys.insert(&declaration.stable_key) {
315 return Err(DeclarationSnapshotError::DuplicateStableKey(
316 declaration.stable_key.clone(),
317 ));
318 }
319 }
320
321 Ok(())
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use crate::slot::MemoryManagerSlot;
328
329 fn declaration(key: &str, id: u8) -> AllocationDeclaration {
330 AllocationDeclaration::new(
331 key,
332 MemoryManagerSlot::new(id).expect("usable slot"),
333 None,
334 SchemaMetadata::default(),
335 )
336 .expect("declaration")
337 }
338
339 #[test]
340 fn declaration_rejects_unbounded_label_metadata() {
341 let err = AllocationDeclaration::new(
342 "app.users.v1",
343 MemoryManagerSlot::new(100).expect("usable slot"),
344 Some("x".repeat(257)),
345 SchemaMetadata::default(),
346 )
347 .expect_err("label too long");
348
349 assert_eq!(err, DeclarationSnapshotError::LabelTooLong);
350 }
351
352 #[test]
353 fn memory_manager_declaration_constructor_builds_common_declaration() {
354 let declaration = AllocationDeclaration::memory_manager("app.orders.v1", 100, "orders")
355 .expect("declaration");
356
357 assert_eq!(declaration.stable_key.as_str(), "app.orders.v1");
358 assert_eq!(
359 declaration.slot,
360 MemoryManagerSlot::new(100).expect("usable slot")
361 );
362 assert_eq!(declaration.label.as_deref(), Some("orders"));
363 assert_eq!(declaration.schema, SchemaMetadata::default());
364 }
365
366 #[test]
367 fn memory_manager_declaration_constructor_rejects_invalid_slot() {
368 let err = AllocationDeclaration::memory_manager("app.orders.v1", u8::MAX, "orders")
369 .expect_err("sentinel must fail");
370
371 assert!(matches!(
372 err,
373 DeclarationSnapshotError::MemoryManagerSlot(_)
374 ));
375 }
376
377 #[test]
378 fn snapshot_decode_rejects_unusable_memory_manager_slot() {
379 let snapshot = DeclarationSnapshot::new(vec![declaration("app.orders.v1", 100)]).unwrap();
380 let mut value = serde_json::to_value(snapshot).unwrap();
381 value["declarations"][0]["slot"]["slot"]["MemoryManagerId"] = serde_json::json!(255);
382 assert!(serde_json::from_value::<DeclarationSnapshot>(value).is_err());
383 }
384
385 #[test]
386 fn snapshot_rejects_unbounded_runtime_fingerprint() {
387 let snapshot =
388 DeclarationSnapshot::new(vec![declaration("app.users.v1", 100)]).expect("snapshot");
389
390 let err = snapshot
391 .with_runtime_fingerprint("x".repeat(257))
392 .expect_err("fingerprint too long");
393
394 assert_eq!(err, DeclarationSnapshotError::RuntimeFingerprintTooLong);
395 }
396
397 #[test]
398 fn rejects_duplicate_keys() {
399 let err = DeclarationSnapshot::new(vec![
400 declaration("app.users.v1", 100),
401 declaration("app.users.v1", 101),
402 ])
403 .expect_err("duplicate key");
404
405 assert_eq!(
406 err,
407 DeclarationSnapshotError::DuplicateStableKey(StableKey::parse("app.users.v1").unwrap())
408 );
409 }
410
411 #[test]
412 fn rejects_duplicate_slots() {
413 for second_key in ["app.orders.v1", "app.users.v1"] {
414 let err = DeclarationSnapshot::new(vec![
415 declaration("app.users.v1", 100),
416 declaration(second_key, 100),
417 ])
418 .expect_err("duplicate slot precedes duplicate key");
419
420 assert_eq!(
421 err,
422 DeclarationSnapshotError::DuplicateSlot(MemoryManagerSlot::new(100).unwrap())
423 );
424 }
425 }
426}