1use crate::{
2 key::{StableKey, StableKeyError},
3 schema::SchemaMetadata,
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 Ok(Self {
46 stable_key,
47 slot,
48 label,
49 schema,
50 })
51 }
52
53 pub fn memory_manager(
55 stable_key: impl AsRef<str>,
56 id: u8,
57 label: impl Into<String>,
58 ) -> Result<Self, DeclarationSnapshotError> {
59 Self::memory_manager_with_schema(stable_key, id, label, SchemaMetadata::default())
60 }
61
62 pub fn memory_manager_unlabeled(
64 stable_key: impl AsRef<str>,
65 id: u8,
66 ) -> Result<Self, DeclarationSnapshotError> {
67 Self::memory_manager_unlabeled_with_schema(stable_key, id, SchemaMetadata::default())
68 }
69
70 pub fn memory_manager_with_schema(
72 stable_key: impl AsRef<str>,
73 id: u8,
74 label: impl Into<String>,
75 schema: SchemaMetadata,
76 ) -> Result<Self, DeclarationSnapshotError> {
77 let slot =
78 MemoryManagerSlot::new(id).map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
79 Self::new(stable_key, slot, Some(label.into()), schema)
80 }
81
82 pub fn memory_manager_unlabeled_with_schema(
84 stable_key: impl AsRef<str>,
85 id: u8,
86 schema: SchemaMetadata,
87 ) -> Result<Self, DeclarationSnapshotError> {
88 let slot =
89 MemoryManagerSlot::new(id).map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
90 Self::new(stable_key, slot, None, schema)
91 }
92
93 #[must_use]
95 pub const fn stable_key(&self) -> &StableKey {
96 &self.stable_key
97 }
98
99 #[must_use]
101 pub const fn slot(&self) -> &MemoryManagerSlot {
102 &self.slot
103 }
104
105 #[must_use]
107 pub fn label(&self) -> Option<&str> {
108 self.label.as_deref()
109 }
110
111 #[must_use]
113 pub const fn schema(&self) -> &SchemaMetadata {
114 &self.schema
115 }
116
117 pub fn validate(&self) -> Result<(), DeclarationSnapshotError> {
120 validate_label(self.label.as_deref())
121 }
122}
123
124#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
135#[serde(deny_unknown_fields)]
136pub struct DeclarationSnapshot {
137 declarations: Vec<AllocationDeclaration>,
139 #[serde(deserialize_with = "crate::cbor::deserialize_present_option")]
141 runtime_fingerprint: Option<String>,
142}
143
144impl DeclarationSnapshot {
145 pub fn new(declarations: Vec<AllocationDeclaration>) -> Result<Self, DeclarationSnapshotError> {
147 validate_declarations(&declarations)?;
148 reject_duplicates(&declarations)?;
149 Ok(Self {
150 declarations,
151 runtime_fingerprint: None,
152 })
153 }
154
155 pub fn with_runtime_fingerprint(
157 mut self,
158 fingerprint: impl Into<String>,
159 ) -> Result<Self, DeclarationSnapshotError> {
160 let fingerprint = fingerprint.into();
161 validate_runtime_fingerprint(Some(&fingerprint))?;
162 self.runtime_fingerprint = Some(fingerprint);
163 Ok(self)
164 }
165
166 #[must_use]
168 pub const fn is_empty(&self) -> bool {
169 self.declarations.is_empty()
170 }
171
172 #[must_use]
174 pub const fn len(&self) -> usize {
175 self.declarations.len()
176 }
177
178 #[must_use]
180 pub fn declarations(&self) -> &[AllocationDeclaration] {
181 &self.declarations
182 }
183
184 #[must_use]
186 pub fn runtime_fingerprint(&self) -> Option<&str> {
187 self.runtime_fingerprint.as_deref()
188 }
189
190 pub fn validate(&self) -> Result<(), DeclarationSnapshotError> {
192 validate_declarations(&self.declarations)?;
193 reject_duplicates(&self.declarations)?;
194 validate_runtime_fingerprint(self.runtime_fingerprint.as_deref())
195 }
196
197 pub(crate) fn into_parts(self) -> (Vec<AllocationDeclaration>, Option<String>) {
198 (self.declarations, self.runtime_fingerprint)
199 }
200}
201
202#[non_exhaustive]
207#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
208pub enum DeclarationSnapshotError {
209 #[error("at most 255 allocation declarations are supported")]
210 TooManyDeclarations,
211 #[error(transparent)]
213 Key(StableKeyError),
214 #[error(transparent)]
216 MemoryManagerSlot(MemoryManagerSlotError),
217 #[error("stable key '{0}' is declared more than once")]
219 DuplicateStableKey(StableKey),
220 #[error("allocation slot '{0:?}' is declared more than once")]
222 DuplicateSlot(MemoryManagerSlot),
223 #[error("allocation declaration label must not be empty when present")]
225 EmptyLabel,
226 #[error("allocation declaration label must be at most 256 bytes")]
228 LabelTooLong,
229 #[error("allocation declaration label must be ASCII")]
231 NonAsciiLabel,
232 #[error("allocation declaration label must not contain ASCII control characters")]
234 ControlCharacterLabel,
235 #[error("runtime_fingerprint must not be empty when present")]
237 EmptyRuntimeFingerprint,
238 #[error("runtime_fingerprint must be at most 256 bytes")]
240 RuntimeFingerprintTooLong,
241 #[error("runtime_fingerprint must be ASCII")]
243 NonAsciiRuntimeFingerprint,
244 #[error("runtime_fingerprint must not contain ASCII control characters")]
246 ControlCharacterRuntimeFingerprint,
247}
248
249fn validate_label(label: Option<&str>) -> Result<(), DeclarationSnapshotError> {
250 let Some(label) = label else {
251 return Ok(());
252 };
253 validate_diagnostic_text(label).map_err(|error| match error {
254 DiagnosticTextError::Empty => DeclarationSnapshotError::EmptyLabel,
255 DiagnosticTextError::TooLong => DeclarationSnapshotError::LabelTooLong,
256 DiagnosticTextError::NonAscii => DeclarationSnapshotError::NonAsciiLabel,
257 DiagnosticTextError::ControlCharacter => DeclarationSnapshotError::ControlCharacterLabel,
258 })
259}
260
261fn validate_declarations(
262 declarations: &[AllocationDeclaration],
263) -> Result<(), DeclarationSnapshotError> {
264 if declarations.len() > crate::constants::MAX_ALLOCATIONS {
265 return Err(DeclarationSnapshotError::TooManyDeclarations);
266 }
267 for declaration in declarations {
268 declaration.validate()?;
269 }
270 Ok(())
271}
272
273pub fn validate_runtime_fingerprint(
274 fingerprint: Option<&str>,
275) -> Result<(), DeclarationSnapshotError> {
276 let Some(fingerprint) = fingerprint else {
277 return Ok(());
278 };
279 validate_diagnostic_text(fingerprint).map_err(|error| match error {
280 DiagnosticTextError::Empty => DeclarationSnapshotError::EmptyRuntimeFingerprint,
281 DiagnosticTextError::TooLong => DeclarationSnapshotError::RuntimeFingerprintTooLong,
282 DiagnosticTextError::NonAscii => DeclarationSnapshotError::NonAsciiRuntimeFingerprint,
283 DiagnosticTextError::ControlCharacter => {
284 DeclarationSnapshotError::ControlCharacterRuntimeFingerprint
285 }
286 })
287}
288
289fn reject_duplicates(
290 declarations: &[AllocationDeclaration],
291) -> Result<(), DeclarationSnapshotError> {
292 let mut keys = BTreeSet::new();
293 let mut slots = [false; crate::constants::MAX_ALLOCATIONS];
294
295 for declaration in declarations {
296 let occupied = &mut slots[usize::from(declaration.slot.id())];
297 if *occupied {
298 return Err(DeclarationSnapshotError::DuplicateSlot(
299 declaration.slot.clone(),
300 ));
301 }
302 *occupied = true;
303 if !keys.insert(&declaration.stable_key) {
304 return Err(DeclarationSnapshotError::DuplicateStableKey(
305 declaration.stable_key.clone(),
306 ));
307 }
308 }
309
310 Ok(())
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use crate::slot::MemoryManagerSlot;
317
318 fn declaration(key: &str, id: u8) -> AllocationDeclaration {
319 AllocationDeclaration::new(
320 key,
321 MemoryManagerSlot::new(id).expect("usable slot"),
322 None,
323 SchemaMetadata::default(),
324 )
325 .expect("declaration")
326 }
327
328 #[test]
329 fn declaration_rejects_unbounded_label_metadata() {
330 let err = AllocationDeclaration::new(
331 "app.users.v1",
332 MemoryManagerSlot::new(100).expect("usable slot"),
333 Some("x".repeat(257)),
334 SchemaMetadata::default(),
335 )
336 .expect_err("label too long");
337
338 assert_eq!(err, DeclarationSnapshotError::LabelTooLong);
339 }
340
341 #[test]
342 fn memory_manager_declaration_constructor_builds_common_declaration() {
343 let declaration = AllocationDeclaration::memory_manager("app.orders.v1", 100, "orders")
344 .expect("declaration");
345
346 assert_eq!(declaration.stable_key.as_str(), "app.orders.v1");
347 assert_eq!(
348 declaration.slot,
349 MemoryManagerSlot::new(100).expect("usable slot")
350 );
351 assert_eq!(declaration.label.as_deref(), Some("orders"));
352 assert_eq!(declaration.schema, SchemaMetadata::default());
353 }
354
355 #[test]
356 fn memory_manager_declaration_constructor_rejects_invalid_slot() {
357 let err = AllocationDeclaration::memory_manager("app.orders.v1", u8::MAX, "orders")
358 .expect_err("sentinel must fail");
359
360 assert!(matches!(
361 err,
362 DeclarationSnapshotError::MemoryManagerSlot(_)
363 ));
364 }
365
366 #[test]
367 fn snapshot_decode_rejects_unusable_memory_manager_slot() {
368 let snapshot = DeclarationSnapshot::new(vec![declaration("app.orders.v1", 100)]).unwrap();
369 let mut value = serde_json::to_value(snapshot).unwrap();
370 value["declarations"][0]["slot"]["slot"]["MemoryManagerId"] = serde_json::json!(255);
371 assert!(serde_json::from_value::<DeclarationSnapshot>(value).is_err());
372 }
373
374 #[test]
375 fn snapshot_rejects_unbounded_runtime_fingerprint() {
376 let snapshot =
377 DeclarationSnapshot::new(vec![declaration("app.users.v1", 100)]).expect("snapshot");
378
379 let err = snapshot
380 .with_runtime_fingerprint("x".repeat(257))
381 .expect_err("fingerprint too long");
382
383 assert_eq!(err, DeclarationSnapshotError::RuntimeFingerprintTooLong);
384 }
385
386 #[test]
387 fn rejects_duplicate_keys() {
388 let err = DeclarationSnapshot::new(vec![
389 declaration("app.users.v1", 100),
390 declaration("app.users.v1", 101),
391 ])
392 .expect_err("duplicate key");
393
394 assert_eq!(
395 err,
396 DeclarationSnapshotError::DuplicateStableKey(StableKey::parse("app.users.v1").unwrap())
397 );
398 }
399
400 #[test]
401 fn rejects_duplicate_slots() {
402 for second_key in ["app.orders.v1", "app.users.v1"] {
403 let err = DeclarationSnapshot::new(vec![
404 declaration("app.users.v1", 100),
405 declaration(second_key, 100),
406 ])
407 .expect_err("duplicate slot precedes duplicate key");
408
409 assert_eq!(
410 err,
411 DeclarationSnapshotError::DuplicateSlot(MemoryManagerSlot::new(100).unwrap())
412 );
413 }
414 }
415}