1use crate::{
2 key::{StableKey, StableKeyError},
3 schema::SchemaMetadata,
4 slot::{MemoryManagerSlot, MemoryManagerSlotError},
5 text::{DiagnosticTextError, validate_diagnostic_text},
6};
7use serde::{Deserialize, Deserializer, Serialize, de::Error as _};
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 = "deserialize_label")]
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
118#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
131pub struct DeclarationSnapshot {
132 declarations: Vec<AllocationDeclaration>,
134 runtime_fingerprint: Option<String>,
136}
137
138impl<'de> Deserialize<'de> for DeclarationSnapshot {
139 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
140 #[derive(Deserialize)]
141 #[serde(rename = "DeclarationSnapshot", deny_unknown_fields)]
142 struct Snapshot {
143 declarations: Vec<AllocationDeclaration>,
144 #[serde(deserialize_with = "crate::cbor::deserialize_present_option")]
145 runtime_fingerprint: Option<String>,
146 }
147
148 let snapshot = Snapshot::deserialize(deserializer)?;
149 let checked = Self::new(snapshot.declarations).map_err(D::Error::custom)?;
152 match snapshot.runtime_fingerprint {
153 Some(fingerprint) => checked
154 .with_runtime_fingerprint(fingerprint)
155 .map_err(D::Error::custom),
156 None => Ok(checked),
157 }
158 }
159}
160
161impl DeclarationSnapshot {
162 pub fn new(declarations: Vec<AllocationDeclaration>) -> Result<Self, DeclarationSnapshotError> {
164 validate_declaration_set(&declarations)?;
165 Ok(Self {
166 declarations,
167 runtime_fingerprint: None,
168 })
169 }
170
171 pub fn with_runtime_fingerprint(
173 mut self,
174 fingerprint: impl Into<String>,
175 ) -> Result<Self, DeclarationSnapshotError> {
176 let fingerprint = fingerprint.into();
177 validate_runtime_fingerprint(Some(&fingerprint))?;
178 self.runtime_fingerprint = Some(fingerprint);
179 Ok(self)
180 }
181
182 #[must_use]
184 pub const fn is_empty(&self) -> bool {
185 self.declarations.is_empty()
186 }
187
188 #[must_use]
190 pub const fn len(&self) -> usize {
191 self.declarations.len()
192 }
193
194 #[must_use]
196 pub fn declarations(&self) -> &[AllocationDeclaration] {
197 &self.declarations
198 }
199
200 #[must_use]
202 pub fn runtime_fingerprint(&self) -> Option<&str> {
203 self.runtime_fingerprint.as_deref()
204 }
205
206 pub(crate) fn into_parts(self) -> (Vec<AllocationDeclaration>, Option<String>) {
207 (self.declarations, self.runtime_fingerprint)
208 }
209}
210
211#[non_exhaustive]
216#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
217pub enum DeclarationSnapshotError {
218 #[error("at most 255 allocation declarations are supported")]
219 TooManyDeclarations,
220 #[error(transparent)]
222 Key(StableKeyError),
223 #[error(transparent)]
225 MemoryManagerSlot(MemoryManagerSlotError),
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 deserialize_label<'de, D: Deserializer<'de>>(
273 deserializer: D,
274) -> Result<Option<String>, D::Error> {
275 let label = Option::<String>::deserialize(deserializer)?;
276 validate_label(label.as_deref()).map_err(D::Error::custom)?;
277 Ok(label)
278}
279
280pub fn validate_runtime_fingerprint(
281 fingerprint: Option<&str>,
282) -> Result<(), DeclarationSnapshotError> {
283 let Some(fingerprint) = fingerprint else {
284 return Ok(());
285 };
286 validate_diagnostic_text(fingerprint).map_err(|error| match error {
287 DiagnosticTextError::Empty => DeclarationSnapshotError::EmptyRuntimeFingerprint,
288 DiagnosticTextError::TooLong => DeclarationSnapshotError::RuntimeFingerprintTooLong,
289 DiagnosticTextError::NonAscii => DeclarationSnapshotError::NonAsciiRuntimeFingerprint,
290 DiagnosticTextError::ControlCharacter => {
291 DeclarationSnapshotError::ControlCharacterRuntimeFingerprint
292 }
293 })
294}
295
296fn validate_declaration_set(
297 declarations: &[AllocationDeclaration],
298) -> Result<(), DeclarationSnapshotError> {
299 if declarations.len() > crate::constants::MAX_ALLOCATIONS {
300 return Err(DeclarationSnapshotError::TooManyDeclarations);
301 }
302 let mut keys = BTreeSet::new();
303 let mut slots = [false; crate::constants::MAX_ALLOCATIONS];
304
305 for declaration in declarations {
306 let occupied = &mut slots[usize::from(declaration.slot.id())];
307 if *occupied {
308 return Err(DeclarationSnapshotError::DuplicateSlot(
309 declaration.slot.clone(),
310 ));
311 }
312 *occupied = true;
313 if !keys.insert(&declaration.stable_key) {
314 return Err(DeclarationSnapshotError::DuplicateStableKey(
315 declaration.stable_key.clone(),
316 ));
317 }
318 }
319
320 Ok(())
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use crate::slot::MemoryManagerSlot;
327
328 fn declaration(key: &str, id: u8) -> AllocationDeclaration {
329 AllocationDeclaration::new(
330 key,
331 MemoryManagerSlot::new(id).expect("usable slot"),
332 None,
333 SchemaMetadata::default(),
334 )
335 .expect("declaration")
336 }
337
338 #[test]
339 fn declaration_rejects_unbounded_label_metadata() {
340 let err = AllocationDeclaration::new(
341 "app.users.v1",
342 MemoryManagerSlot::new(100).expect("usable slot"),
343 Some("x".repeat(257)),
344 SchemaMetadata::default(),
345 )
346 .expect_err("label too long");
347
348 assert_eq!(err, DeclarationSnapshotError::LabelTooLong);
349 }
350
351 #[test]
352 fn memory_manager_declaration_constructor_builds_common_declaration() {
353 let declaration = AllocationDeclaration::memory_manager("app.orders.v1", 100, "orders")
354 .expect("declaration");
355
356 assert_eq!(declaration.stable_key.as_str(), "app.orders.v1");
357 assert_eq!(
358 declaration.slot,
359 MemoryManagerSlot::new(100).expect("usable slot")
360 );
361 assert_eq!(declaration.label.as_deref(), Some("orders"));
362 assert_eq!(declaration.schema, SchemaMetadata::default());
363 }
364
365 #[test]
366 fn memory_manager_declaration_constructor_rejects_invalid_slot() {
367 let err = AllocationDeclaration::memory_manager("app.orders.v1", u8::MAX, "orders")
368 .expect_err("sentinel must fail");
369
370 assert!(matches!(
371 err,
372 DeclarationSnapshotError::MemoryManagerSlot(_)
373 ));
374 }
375
376 #[test]
377 fn snapshot_decode_rejects_unusable_memory_manager_slot() {
378 let snapshot = DeclarationSnapshot::new(vec![declaration("app.orders.v1", 100)]).unwrap();
379 let mut value = serde_json::to_value(snapshot).unwrap();
380 value["declarations"][0]["slot"]["slot"]["MemoryManagerId"] = serde_json::json!(255);
381 assert!(serde_json::from_value::<DeclarationSnapshot>(value).is_err());
382 }
383
384 #[test]
385 fn snapshot_rejects_unbounded_runtime_fingerprint() {
386 let snapshot =
387 DeclarationSnapshot::new(vec![declaration("app.users.v1", 100)]).expect("snapshot");
388
389 let err = snapshot
390 .with_runtime_fingerprint("x".repeat(257))
391 .expect_err("fingerprint too long");
392
393 assert_eq!(err, DeclarationSnapshotError::RuntimeFingerprintTooLong);
394 }
395
396 #[test]
397 fn rejects_duplicate_keys() {
398 let err = DeclarationSnapshot::new(vec![
399 declaration("app.users.v1", 100),
400 declaration("app.users.v1", 101),
401 ])
402 .expect_err("duplicate key");
403
404 assert_eq!(
405 err,
406 DeclarationSnapshotError::DuplicateStableKey(StableKey::parse("app.users.v1").unwrap())
407 );
408 }
409
410 #[test]
411 fn rejects_duplicate_slots() {
412 for second_key in ["app.orders.v1", "app.users.v1"] {
413 let err = DeclarationSnapshot::new(vec![
414 declaration("app.users.v1", 100),
415 declaration(second_key, 100),
416 ])
417 .expect_err("duplicate slot precedes duplicate key");
418
419 assert_eq!(
420 err,
421 DeclarationSnapshotError::DuplicateSlot(MemoryManagerSlot::new(100).unwrap())
422 );
423 }
424 }
425}