Skip to main content

ic_memory/
declaration.rs

1use crate::{
2    key::{StableKey, StableKeyError},
3    schema::{SchemaMetadata, SchemaMetadataError},
4    slot::AllocationSlotDescriptor,
5};
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeSet;
8
9///
10/// AllocationDeclaration
11///
12/// Data-only claim that a stable key owns an allocation slot.
13#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
14pub struct AllocationDeclaration {
15    /// Durable stable key.
16    pub stable_key: StableKey,
17    /// Claimed allocation slot.
18    pub slot: AllocationSlotDescriptor,
19    /// Optional diagnostic label.
20    pub label: Option<String>,
21    /// Optional diagnostic schema metadata.
22    pub schema: SchemaMetadata,
23}
24
25impl AllocationDeclaration {
26    /// Build a declaration from raw parts.
27    pub fn new(
28        stable_key: impl AsRef<str>,
29        slot: AllocationSlotDescriptor,
30        label: Option<String>,
31        schema: SchemaMetadata,
32    ) -> Result<Self, DeclarationSnapshotError> {
33        let stable_key = StableKey::parse(stable_key).map_err(DeclarationSnapshotError::Key)?;
34        schema
35            .validate()
36            .map_err(DeclarationSnapshotError::SchemaMetadata)?;
37        Ok(Self {
38            stable_key,
39            slot,
40            label,
41            schema,
42        })
43    }
44}
45
46///
47/// DeclarationCollector
48///
49/// Mutable collection phase before a snapshot is sealed.
50#[derive(Clone, Debug, Default)]
51pub struct DeclarationCollector {
52    declarations: Vec<AllocationDeclaration>,
53}
54
55impl DeclarationCollector {
56    /// Add one allocation declaration.
57    pub fn push(&mut self, declaration: AllocationDeclaration) {
58        self.declarations.push(declaration);
59    }
60
61    /// Seal collected declarations into a duplicate-free snapshot.
62    pub fn seal(self) -> Result<DeclarationSnapshot, DeclarationSnapshotError> {
63        DeclarationSnapshot::new(self.declarations)
64    }
65}
66
67///
68/// DeclarationSnapshot
69///
70/// Immutable runtime declaration snapshot ready for policy and history validation.
71#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
72pub struct DeclarationSnapshot {
73    /// Runtime declarations.
74    declarations: Vec<AllocationDeclaration>,
75    /// Optional binary/runtime identity for generation diagnostics.
76    runtime_fingerprint: Option<String>,
77}
78
79impl DeclarationSnapshot {
80    /// Create and validate a declaration snapshot.
81    pub fn new(declarations: Vec<AllocationDeclaration>) -> Result<Self, DeclarationSnapshotError> {
82        reject_duplicates(&declarations)?;
83        Ok(Self {
84            declarations,
85            runtime_fingerprint: None,
86        })
87    }
88
89    /// Attach an optional runtime fingerprint.
90    #[must_use]
91    pub fn with_runtime_fingerprint(mut self, fingerprint: impl Into<String>) -> Self {
92        self.runtime_fingerprint = Some(fingerprint.into());
93        self
94    }
95
96    /// Return true when the snapshot has no declarations.
97    #[must_use]
98    pub const fn is_empty(&self) -> bool {
99        self.declarations.is_empty()
100    }
101
102    /// Return the number of declarations in the snapshot.
103    #[must_use]
104    pub const fn len(&self) -> usize {
105        self.declarations.len()
106    }
107
108    /// Borrow the sealed declarations.
109    #[must_use]
110    pub fn declarations(&self) -> &[AllocationDeclaration] {
111        &self.declarations
112    }
113
114    /// Borrow the optional runtime fingerprint.
115    #[must_use]
116    pub fn runtime_fingerprint(&self) -> Option<&str> {
117        self.runtime_fingerprint.as_deref()
118    }
119
120    pub(crate) fn into_parts(self) -> (Vec<AllocationDeclaration>, Option<String>) {
121        (self.declarations, self.runtime_fingerprint)
122    }
123}
124
125///
126/// DeclarationSnapshotError
127///
128/// Declaration snapshot validation failure.
129#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
130pub enum DeclarationSnapshotError {
131    /// Stable-key grammar failure.
132    #[error(transparent)]
133    Key(StableKeyError),
134    /// Schema metadata encoding failure.
135    #[error(transparent)]
136    SchemaMetadata(SchemaMetadataError),
137    /// A stable key appeared more than once in one snapshot.
138    #[error("stable key '{0}' is declared more than once")]
139    DuplicateStableKey(StableKey),
140    /// An allocation slot appeared more than once in one snapshot.
141    #[error("allocation slot '{0:?}' is declared more than once")]
142    DuplicateSlot(AllocationSlotDescriptor),
143}
144
145fn reject_duplicates(
146    declarations: &[AllocationDeclaration],
147) -> Result<(), DeclarationSnapshotError> {
148    let mut keys = BTreeSet::new();
149    let mut slots = BTreeSet::new();
150
151    for declaration in declarations {
152        if !slots.insert(declaration.slot.clone()) {
153            return Err(DeclarationSnapshotError::DuplicateSlot(
154                declaration.slot.clone(),
155            ));
156        }
157        if !keys.insert(declaration.stable_key.clone()) {
158            return Err(DeclarationSnapshotError::DuplicateStableKey(
159                declaration.stable_key.clone(),
160            ));
161        }
162    }
163
164    Ok(())
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use crate::slot::AllocationSlotDescriptor;
171
172    fn declaration(key: &str, id: u8) -> AllocationDeclaration {
173        AllocationDeclaration::new(
174            key,
175            AllocationSlotDescriptor::memory_manager(id),
176            None,
177            SchemaMetadata::default(),
178        )
179        .expect("declaration")
180    }
181
182    #[test]
183    fn rejects_duplicate_keys() {
184        let err = DeclarationSnapshot::new(vec![
185            declaration("app.users.v1", 100),
186            declaration("app.users.v1", 101),
187        ])
188        .expect_err("duplicate key");
189
190        assert!(matches!(
191            err,
192            DeclarationSnapshotError::DuplicateStableKey(_)
193        ));
194    }
195
196    #[test]
197    fn rejects_duplicate_slots() {
198        let err = DeclarationSnapshot::new(vec![
199            declaration("app.users.v1", 100),
200            declaration("app.orders.v1", 100),
201        ])
202        .expect_err("duplicate slot");
203
204        assert!(matches!(err, DeclarationSnapshotError::DuplicateSlot(_)));
205    }
206}