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