zerodds_corba_ccm_lib/
persistence.rs1use alloc::boxed::Box;
16use alloc::collections::BTreeMap;
17use alloc::vec::Vec;
18
19use zerodds_corba_ccm::cif::{CifError, ComponentExecutor};
20use zerodds_corba_ccm::context::ComponentContext;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct StorageEntry {
25 pub oid: Vec<u8>,
27 pub state: Vec<u8>,
29 pub dirty: bool,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum PersistenceError {
36 NotFound(Vec<u8>),
38 DuplicateOid(Vec<u8>),
40 Cif(CifError),
42}
43
44impl core::fmt::Display for PersistenceError {
45 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46 match self {
47 Self::NotFound(oid) => write!(f, "object not found: {oid:?}"),
48 Self::DuplicateOid(oid) => write!(f, "duplicate oid: {oid:?}"),
49 Self::Cif(e) => write!(f, "cif: {e:?}"),
50 }
51 }
52}
53
54#[cfg(feature = "std")]
55impl std::error::Error for PersistenceError {}
56
57#[derive(Default)]
59pub struct PersistenceStorageComponent {
60 storage: BTreeMap<Vec<u8>, StorageEntry>,
61 activated: bool,
62 flush_count: u64,
63 ctx: Option<Box<dyn ComponentContext>>,
64}
65
66impl core::fmt::Debug for PersistenceStorageComponent {
67 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68 f.debug_struct("PersistenceStorageComponent")
69 .field("entries", &self.storage.len())
70 .field("activated", &self.activated)
71 .field("flush_count", &self.flush_count)
72 .finish_non_exhaustive()
73 }
74}
75
76impl PersistenceStorageComponent {
77 #[must_use]
79 pub fn new() -> Self {
80 Self::default()
81 }
82
83 pub fn create(&mut self, oid: Vec<u8>, state: Vec<u8>) -> Result<(), PersistenceError> {
88 if self.storage.contains_key(&oid) {
89 return Err(PersistenceError::DuplicateOid(oid));
90 }
91 self.storage.insert(
92 oid.clone(),
93 StorageEntry {
94 oid,
95 state,
96 dirty: true,
97 },
98 );
99 Ok(())
100 }
101
102 #[must_use]
104 pub fn find(&self, oid: &[u8]) -> Option<&StorageEntry> {
105 self.storage.get(oid)
106 }
107
108 pub fn update(&mut self, oid: &[u8], new_state: Vec<u8>) -> Result<(), PersistenceError> {
113 let entry = self
114 .storage
115 .get_mut(oid)
116 .ok_or_else(|| PersistenceError::NotFound(oid.to_vec()))?;
117 entry.state = new_state;
118 entry.dirty = true;
119 Ok(())
120 }
121
122 pub fn destroy(&mut self, oid: &[u8]) -> Result<(), PersistenceError> {
127 if self.storage.remove(oid).is_none() {
128 return Err(PersistenceError::NotFound(oid.to_vec()));
129 }
130 Ok(())
131 }
132
133 pub fn flush(&mut self) -> u64 {
136 for entry in self.storage.values_mut() {
137 entry.dirty = false;
138 }
139 self.flush_count += 1;
140 self.flush_count
141 }
142
143 #[must_use]
145 pub fn len(&self) -> usize {
146 self.storage.len()
147 }
148
149 #[must_use]
151 pub fn is_empty(&self) -> bool {
152 self.storage.is_empty()
153 }
154
155 #[must_use]
157 pub fn flush_count(&self) -> u64 {
158 self.flush_count
159 }
160
161 #[must_use]
163 pub fn dirty_count(&self) -> usize {
164 self.storage.values().filter(|e| e.dirty).count()
165 }
166
167 #[must_use]
169 pub fn is_active(&self) -> bool {
170 self.activated
171 }
172}
173
174impl ComponentExecutor for PersistenceStorageComponent {
175 fn set_context(&mut self, context: Box<dyn ComponentContext>) {
176 self.ctx = Some(context);
177 }
178
179 fn ccm_activate(&mut self) -> Result<(), CifError> {
180 self.activated = true;
181 Ok(())
182 }
183
184 fn ccm_passivate(&mut self) -> Result<(), CifError> {
185 let _ = self.flush();
188 self.activated = false;
189 Ok(())
190 }
191
192 fn ccm_remove(&mut self) -> Result<(), CifError> {
193 self.activated = false;
194 self.storage.clear();
195 Ok(())
196 }
197}
198
199#[cfg(test)]
200#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn create_and_find_round_trip() {
206 let mut s = PersistenceStorageComponent::new();
207 s.create(b"oid1".to_vec(), b"state-A".to_vec()).unwrap();
208 let entry = s.find(b"oid1").unwrap();
209 assert_eq!(entry.state, b"state-A");
210 assert!(entry.dirty);
211 }
212
213 #[test]
214 fn duplicate_create_rejected() {
215 let mut s = PersistenceStorageComponent::new();
216 s.create(b"oid1".to_vec(), alloc::vec![]).unwrap();
217 assert!(matches!(
218 s.create(b"oid1".to_vec(), alloc::vec![]),
219 Err(PersistenceError::DuplicateOid(_))
220 ));
221 }
222
223 #[test]
224 fn update_changes_state_and_marks_dirty() {
225 let mut s = PersistenceStorageComponent::new();
226 s.create(b"oid".to_vec(), b"old".to_vec()).unwrap();
227 s.flush();
228 assert_eq!(s.dirty_count(), 0);
229 s.update(b"oid", b"new".to_vec()).unwrap();
230 assert_eq!(s.find(b"oid").unwrap().state, b"new");
231 assert_eq!(s.dirty_count(), 1);
232 }
233
234 #[test]
235 fn update_unknown_fails() {
236 let mut s = PersistenceStorageComponent::new();
237 assert!(matches!(
238 s.update(b"nope", alloc::vec![]),
239 Err(PersistenceError::NotFound(_))
240 ));
241 }
242
243 #[test]
244 fn destroy_removes_entry() {
245 let mut s = PersistenceStorageComponent::new();
246 s.create(b"oid".to_vec(), alloc::vec![]).unwrap();
247 s.destroy(b"oid").unwrap();
248 assert!(s.is_empty());
249 }
250
251 #[test]
252 fn flush_resets_dirty_and_increments_counter() {
253 let mut s = PersistenceStorageComponent::new();
254 s.create(b"a".to_vec(), alloc::vec![]).unwrap();
255 s.create(b"b".to_vec(), alloc::vec![]).unwrap();
256 assert_eq!(s.dirty_count(), 2);
257 let n = s.flush();
258 assert_eq!(n, 1);
259 assert_eq!(s.dirty_count(), 0);
260 assert_eq!(s.flush_count(), 1);
261 }
262
263 #[test]
264 fn passivate_flushes_dirty_entries() {
265 let mut s = PersistenceStorageComponent::new();
266 s.ccm_activate().unwrap();
267 s.create(b"a".to_vec(), alloc::vec![1]).unwrap();
268 assert_eq!(s.dirty_count(), 1);
269 s.ccm_passivate().unwrap();
270 assert_eq!(s.dirty_count(), 0);
271 assert!(!s.is_active());
272 }
273
274 #[test]
275 fn remove_clears_all() {
276 let mut s = PersistenceStorageComponent::new();
277 s.create(b"a".to_vec(), alloc::vec![]).unwrap();
278 s.ccm_remove().unwrap();
279 assert!(s.is_empty());
280 }
281}