1use crate::{
2 key::{StableKey, StableKeyError},
3 schema::{SchemaMetadata, SchemaMetadataError},
4 slot::AllocationSlotDescriptor,
5};
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeSet;
8
9const DIAGNOSTIC_STRING_MAX_BYTES: usize = 256;
10
11#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
16pub struct AllocationDeclaration {
17 pub stable_key: StableKey,
19 pub slot: AllocationSlotDescriptor,
21 pub label: Option<String>,
23 pub schema: SchemaMetadata,
25}
26
27impl AllocationDeclaration {
28 pub fn new(
30 stable_key: impl AsRef<str>,
31 slot: AllocationSlotDescriptor,
32 label: Option<String>,
33 schema: SchemaMetadata,
34 ) -> Result<Self, DeclarationSnapshotError> {
35 let stable_key = StableKey::parse(stable_key).map_err(DeclarationSnapshotError::Key)?;
36 validate_label(label.as_deref())?;
37 schema
38 .validate()
39 .map_err(DeclarationSnapshotError::SchemaMetadata)?;
40 Ok(Self {
41 stable_key,
42 slot,
43 label,
44 schema,
45 })
46 }
47}
48
49#[derive(Clone, Debug, Default)]
54pub struct DeclarationCollector {
55 declarations: Vec<AllocationDeclaration>,
56}
57
58impl DeclarationCollector {
59 pub fn push(&mut self, declaration: AllocationDeclaration) {
61 self.declarations.push(declaration);
62 }
63
64 pub fn seal(self) -> Result<DeclarationSnapshot, DeclarationSnapshotError> {
66 DeclarationSnapshot::new(self.declarations)
67 }
68}
69
70#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
75pub struct DeclarationSnapshot {
76 declarations: Vec<AllocationDeclaration>,
78 runtime_fingerprint: Option<String>,
80}
81
82impl DeclarationSnapshot {
83 pub fn new(declarations: Vec<AllocationDeclaration>) -> Result<Self, DeclarationSnapshotError> {
85 for declaration in &declarations {
86 validate_label(declaration.label.as_deref())?;
87 declaration
88 .schema
89 .validate()
90 .map_err(DeclarationSnapshotError::SchemaMetadata)?;
91 }
92 reject_duplicates(&declarations)?;
93 Ok(Self {
94 declarations,
95 runtime_fingerprint: None,
96 })
97 }
98
99 pub fn with_runtime_fingerprint(
101 mut self,
102 fingerprint: impl Into<String>,
103 ) -> Result<Self, DeclarationSnapshotError> {
104 let fingerprint = fingerprint.into();
105 validate_runtime_fingerprint(Some(&fingerprint))?;
106 self.runtime_fingerprint = Some(fingerprint);
107 Ok(self)
108 }
109
110 #[must_use]
112 pub fn is_empty(&self) -> bool {
113 self.declarations.is_empty()
114 }
115
116 #[must_use]
118 pub fn len(&self) -> usize {
119 self.declarations.len()
120 }
121
122 #[must_use]
124 pub fn declarations(&self) -> &[AllocationDeclaration] {
125 &self.declarations
126 }
127
128 #[must_use]
130 pub fn runtime_fingerprint(&self) -> Option<&str> {
131 self.runtime_fingerprint.as_deref()
132 }
133
134 pub(crate) fn into_parts(self) -> (Vec<AllocationDeclaration>, Option<String>) {
135 (self.declarations, self.runtime_fingerprint)
136 }
137}
138
139#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
144pub enum DeclarationSnapshotError {
145 #[error(transparent)]
147 Key(StableKeyError),
148 #[error(transparent)]
150 SchemaMetadata(SchemaMetadataError),
151 #[error("stable key '{0}' is declared more than once")]
153 DuplicateStableKey(StableKey),
154 #[error("allocation slot '{0:?}' is declared more than once")]
156 DuplicateSlot(AllocationSlotDescriptor),
157 #[error("allocation declaration label must not be empty when present")]
159 EmptyLabel,
160 #[error("allocation declaration label must be at most 256 bytes")]
162 LabelTooLong,
163 #[error("allocation declaration label must be ASCII")]
165 NonAsciiLabel,
166 #[error("allocation declaration label must not contain ASCII control characters")]
168 ControlCharacterLabel,
169 #[error("runtime_fingerprint must not be empty when present")]
171 EmptyRuntimeFingerprint,
172 #[error("runtime_fingerprint must be at most 256 bytes")]
174 RuntimeFingerprintTooLong,
175 #[error("runtime_fingerprint must be ASCII")]
177 NonAsciiRuntimeFingerprint,
178 #[error("runtime_fingerprint must not contain ASCII control characters")]
180 ControlCharacterRuntimeFingerprint,
181}
182
183fn validate_label(label: Option<&str>) -> Result<(), DeclarationSnapshotError> {
184 let Some(label) = label else {
185 return Ok(());
186 };
187 if label.is_empty() {
188 return Err(DeclarationSnapshotError::EmptyLabel);
189 }
190 if label.len() > DIAGNOSTIC_STRING_MAX_BYTES {
191 return Err(DeclarationSnapshotError::LabelTooLong);
192 }
193 if !label.is_ascii() {
194 return Err(DeclarationSnapshotError::NonAsciiLabel);
195 }
196 if label.bytes().any(|byte| byte.is_ascii_control()) {
197 return Err(DeclarationSnapshotError::ControlCharacterLabel);
198 }
199 Ok(())
200}
201
202pub(crate) fn validate_runtime_fingerprint(
203 fingerprint: Option<&str>,
204) -> Result<(), DeclarationSnapshotError> {
205 let Some(fingerprint) = fingerprint else {
206 return Ok(());
207 };
208 if fingerprint.is_empty() {
209 return Err(DeclarationSnapshotError::EmptyRuntimeFingerprint);
210 }
211 if fingerprint.len() > DIAGNOSTIC_STRING_MAX_BYTES {
212 return Err(DeclarationSnapshotError::RuntimeFingerprintTooLong);
213 }
214 if !fingerprint.is_ascii() {
215 return Err(DeclarationSnapshotError::NonAsciiRuntimeFingerprint);
216 }
217 if fingerprint.bytes().any(|byte| byte.is_ascii_control()) {
218 return Err(DeclarationSnapshotError::ControlCharacterRuntimeFingerprint);
219 }
220 Ok(())
221}
222
223fn reject_duplicates(
224 declarations: &[AllocationDeclaration],
225) -> Result<(), DeclarationSnapshotError> {
226 let mut keys = BTreeSet::new();
227 let mut slots = BTreeSet::new();
228
229 for declaration in declarations {
230 if !slots.insert(declaration.slot.clone()) {
231 return Err(DeclarationSnapshotError::DuplicateSlot(
232 declaration.slot.clone(),
233 ));
234 }
235 if !keys.insert(declaration.stable_key.clone()) {
236 return Err(DeclarationSnapshotError::DuplicateStableKey(
237 declaration.stable_key.clone(),
238 ));
239 }
240 }
241
242 Ok(())
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use crate::slot::AllocationSlotDescriptor;
249
250 fn declaration(key: &str, id: u8) -> AllocationDeclaration {
251 AllocationDeclaration::new(
252 key,
253 AllocationSlotDescriptor::memory_manager(id).expect("usable slot"),
254 None,
255 SchemaMetadata::default(),
256 )
257 .expect("declaration")
258 }
259
260 #[test]
261 fn declaration_rejects_unbounded_label_metadata() {
262 let err = AllocationDeclaration::new(
263 "app.users.v1",
264 AllocationSlotDescriptor::memory_manager(100).expect("usable slot"),
265 Some("x".repeat(257)),
266 SchemaMetadata::default(),
267 )
268 .expect_err("label too long");
269
270 assert_eq!(err, DeclarationSnapshotError::LabelTooLong);
271 }
272
273 #[test]
274 fn snapshot_rejects_unbounded_runtime_fingerprint() {
275 let snapshot =
276 DeclarationSnapshot::new(vec![declaration("app.users.v1", 100)]).expect("snapshot");
277
278 let err = snapshot
279 .with_runtime_fingerprint("x".repeat(257))
280 .expect_err("fingerprint too long");
281
282 assert_eq!(err, DeclarationSnapshotError::RuntimeFingerprintTooLong);
283 }
284
285 #[test]
286 fn rejects_duplicate_keys() {
287 let err = DeclarationSnapshot::new(vec![
288 declaration("app.users.v1", 100),
289 declaration("app.users.v1", 101),
290 ])
291 .expect_err("duplicate key");
292
293 assert!(matches!(
294 err,
295 DeclarationSnapshotError::DuplicateStableKey(_)
296 ));
297 }
298
299 #[test]
300 fn rejects_duplicate_slots() {
301 let err = DeclarationSnapshot::new(vec![
302 declaration("app.users.v1", 100),
303 declaration("app.orders.v1", 100),
304 ])
305 .expect_err("duplicate slot");
306
307 assert!(matches!(err, DeclarationSnapshotError::DuplicateSlot(_)));
308 }
309}